Сохраняйте отображение текущей страницы до тех пор, пока новая страница не будет загружена с помощью React .lazy и React .Ожидание

#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 компонент загружен, просто отобразить его и заменить домашнюю страницу?

Редактировать happy-cohen-z60b9

Ответ №1:

В настоящее время это возможно только в экспериментальном параллельном режиме. Вам придется подождать, пока это станет общедоступным, прежде чем использовать это на рабочем сайте.

Комментарии:

1. О, хорошо, не могли бы вы поделиться примером того, как выглядит реализация в экспериментальном режиме? 🙂

2. @johannchopin Проблема в том, что параллельный режим предполагает использование useTransition() перехвата, но в настоящее время не поддерживается react-router .

3. Хорошо, я вижу, поэтому мне нужно будет подождать, я думаю: p

Ответ №2:

Добрый день, я считаю, что ваша цель может быть достигнута с помощью react-router v6: loader

Кроме того, хорошая статья из официальных документов о руководстве по отложенным данным

Объяснение состоит из двух частей:

  1. Первая часть: основная идея заключается в том, что функция loader может быть асинхронной, и если вы ожидаете, что ваши данные выборки внутри фрагмента компонента не запускаются и будут ждать этих данных перед вызовом элемента в конфигурации маршрутизатора.

Таким образом, это позволяет вам «сохранять отображение текущей страницы до загрузки новой страницы»

  1. Вторая часть: но если вы удалите await и вернете новое обещание в качестве ответа, ваш блок отложенного компонента начнет загружаться одновременно с выборкой данных.

Таким образом, это позволяет отображать пользовательский интерфейс (а не пустой экран) и загрузчик во время извлечения данных в процессе

Заключение:

Итак, моя идея заключается в том, что для начальной загрузки мы используем «вторую часть», поэтому загружаем фрагмент с выборкой параллельно, а для следующего ввода в этот маршрут загружаем в загрузчик необходимые фрагменты извлекаем данные параллельно и ожидаем оба в загрузчике перед переходом к компоненту

Давайте углубимся в пример:

  1. У нас есть маршрутизатор с асинхронным загрузчиком, который при начальной загрузке создает внутри обещание выборки и проходит через себя, поэтому загрузка выборки и фрагмента загружается одновременно. И для последующих загрузок ожидайте выборки и фрагментов параллельно (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,
        };
    };
  
  1. Установите этот загрузчик в конфигурацию маршрутизатора:
     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;

  

В дополнение к отображению загрузчика я предлагаю создать для него глобальный загрузчик, мы должны поиграть с ним:

  1. Для отображения глобального загрузчика мы можем создать компонент и получить состояние загрузки и показать загрузчик, если состояние === ‘загрузка’
     const GlobalLoader = () => {
      const { state } = useNavigation();
      const isEnabled = state === 'loading';

      if (isEnabled) {
        return state;
      }

      return null;
    };
  

Надеюсь, это поможет вам и подтолкнет к отличным решениям

Ответ №3:

Вы можете использовать этот пакет для архивирования текущей страницы в качестве запасного варианта.

Прочитайте инструкции для получения более подробной информации по ссылке.

Комментарии:

1. Кажется интересным, я посмотрю!