#javascript #reactjs #react-router #react-suspense #react-lazy-load
#javascript #reactjs #react-router #реакция-приостановка #react-lazy-load
Вопрос:
Я использую React router для отображения разных страниц в соответствии с определенным URL. Теперь я хотел использовать React .ленивая загрузка всех компонентов моей страницы:
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
const Page = React.lazy(() => {
return import("./Page");
});
const App = () => {
return (
<Router>
<React.Suspense fallback={<div>Loading page...</div>}>
<Switch>
<Route exact path="/">
<h1>Home</h1>
<Link to="/page">Go to another page</Link>
</Route>
<Route path="/page" component={Page} />
</Switch>
</React.Suspense>
</Router>
);
};
export default App;
Это работает действительно хорошо, но когда я захожу /page
, все мои Home
исчезают, и я вижу только резервный Loading page...
компонент (страница исчезает, затем быстро появляется другая, что беспокоит пользователя).
Это поведение по умолчанию React.Suspense
, но есть ли способ в этом случае сохранить фактическую домашнюю страницу, отображаемую на экране с Loading page...
сообщением вверху, а когда Page
компонент загружен, просто отобразить его и заменить домашнюю страницу?
Ответ №1:
В настоящее время это возможно только в экспериментальном параллельном режиме. Вам придется подождать, пока это станет общедоступным, прежде чем использовать это на рабочем сайте.
Комментарии:
1. О, хорошо, не могли бы вы поделиться примером того, как выглядит реализация в экспериментальном режиме? 🙂
2. @johannchopin Проблема в том, что параллельный режим предполагает использование
useTransition()
перехвата, но в настоящее время не поддерживается react-router .3. Хорошо, я вижу, поэтому мне нужно будет подождать, я думаю: p
Ответ №2:
Добрый день, я считаю, что ваша цель может быть достигнута с помощью react-router v6: loader
Кроме того, хорошая статья из официальных документов о руководстве по отложенным данным
Объяснение состоит из двух частей:
- Первая часть: основная идея заключается в том, что функция loader может быть асинхронной, и если вы ожидаете, что ваши данные выборки внутри фрагмента компонента не запускаются и будут ждать этих данных перед вызовом элемента в конфигурации маршрутизатора.
Таким образом, это позволяет вам «сохранять отображение текущей страницы до загрузки новой страницы»
- Вторая часть: но если вы удалите await и вернете новое обещание в качестве ответа, ваш блок отложенного компонента начнет загружаться одновременно с выборкой данных.
Таким образом, это позволяет отображать пользовательский интерфейс (а не пустой экран) и загрузчик во время извлечения данных в процессе
Заключение:
Итак, моя идея заключается в том, что для начальной загрузки мы используем «вторую часть», поэтому загружаем фрагмент с выборкой параллельно, а для следующего ввода в этот маршрут загружаем в загрузчик необходимые фрагменты извлекаем данные параллельно и ожидаем оба в загрузчике перед переходом к компоненту
Давайте углубимся в пример:
- У нас есть маршрутизатор с асинхронным загрузчиком, который при начальной загрузке создает внутри обещание выборки и проходит через себя, поэтому загрузка выборки и фрагмента загружается одновременно. И для последующих загрузок ожидайте выборки и фрагментов параллельно (Promise.all, Promise.allSettled)
import { defer } from 'react-router-dom';
// store if it is initial load
let isInitialLoad = true;
const loaderDetails = async ({ request, params }) => {
const { yourParam } = params;
if (!yourParam) {
throw new Error('Error');
}
const responsePromise = fetch(
`url/${yourParam}`,
{
signal: request.signal,
},
).then((res) => res.json());
if (isInitialLoad) {
isInitialLoad = false;
return defer({ responsePromise });
}
// For subsequences calls we load all chunks fetch data and await for result before further transfer
const [response] = await Promise.all([
responsePromise,
// ... other chunks
]);
return {
responsePromise: response,
};
};
- Установите этот загрузчик в конфигурацию маршрутизатора:
const ROUTES = createBrowserRoutes({
{
path: '*',
element: () => import('YourContainerComponent'),
/**
* FYI: loader could be sync or async
* so we can apply here dynamic import
* This solves the problem when we have tons of route configurations
* and we load loader logic only for a particular route
* BUT this causes another HUGE problem:
* we must wait for dynamic loader import and only then start
* loading lazy component from the element prop.
*
* So we should make a call for all needed chunks inside the loader.
**/
loader: async (props) => {
Promise.all(import('loaderDetails'), ...otherChunks);
const loader = await import('loaderDetails');
return loader.default(props);
},
/* Sync approach doesn't have this problem
* BUT have another:
* Let's imagine we have 25 pages and load only one
* for this case we load all loader logic
* let's assume loader size is 20kb so it is 0.5 mb size total
* This is huge, we don't need 480kb for displaying our page
* My conclusion: we should almost always strive to first approach
*/
// loader: loaderDetails,
});
и инициализируйте в приложении:
import { Suspense } from 'react';
import { RouterProvider } from 'react-router-dom';
/**
* Suspense need if we assign async requests in ROUTES config
**/
const App = () => (
<Suspense>
<RouterProvider router={ROUTES} />
</Suspense>
);
Извлеките эти данные в определенном компоненте из react-router:
const YourContainerComponent = () => {
const loaderResponse = useLoaderData(); // return { responsePromise }
return (
<Suspense>
<Await
key={'UNIQUE_KEY_PROP_IDENTIFIER'}
resolve={loaderResponse.responsePromise}
errorElement={'some error'}
>
<YourComponent />
</Await>
</SuspenseDefault>
)
}
const YourPresentationComponent = () => {
// get data direct here
// component wait for retrieved data
// while he waiting on UI shows a fallback property from <Suspense fallback={undefined}>
const loaderData = useAsyncValue();
return (
<>
Show data: {JSON.stringify(loaderData)}
</>
);
}
export default YourContainerComponent;
В дополнение к отображению загрузчика я предлагаю создать для него глобальный загрузчик, мы должны поиграть с ним:
- Для отображения глобального загрузчика мы можем создать компонент и получить состояние загрузки и показать загрузчик, если состояние === ‘загрузка’
const GlobalLoader = () => {
const { state } = useNavigation();
const isEnabled = state === 'loading';
if (isEnabled) {
return state;
}
return null;
};
Надеюсь, это поможет вам и подтолкнет к отличным решениям
Ответ №3:
Вы можете использовать этот пакет для архивирования текущей страницы в качестве запасного варианта.
Прочитайте инструкции для получения более подробной информации по ссылке.
Комментарии:
1. Кажется интересным, я посмотрю!