Разделение кода с помощью React.lazy и Suspense

Вам никогда не придется отправлять пользователям больше кода, чем необходимо, поэтому разделите пакеты, чтобы этого никогда не произошло!

Метод React.lazy позволяет легко разделить код приложения React на уровне компонентов с помощью динамического импорта.

import React, { lazy } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

Почему это полезно?

Большое приложение React обычно состоит из множества компонентов, служебных методов и сторонних библиотек. Если не предпринимаются попытки загружать различные части приложения только тогда, когда они необходимы, вашим пользователям будет отправлен один большой пакет JavaScript, как только они загрузят первую страницу. Это может существенно повлиять на производительность страницы.

Функция React.lazy предоставляет встроенный способ разделения компонентов приложения на отдельные фрагменты JavaScript с минимальными затратами усилий. Затем вы можете позаботиться о состояниях загрузки, соединив их с компонентом Suspense .

Саспенс

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

Однако при загрузке компонента с разделением кода по сети пользователям всегда приходится сталкиваться с небольшой задержкой, поэтому важно отображать полезное состояние загрузки. Использование React.lazy с компонентом Suspense помогает решить эту проблему.

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense принимает fallback компонент, который позволяет отображать любой компонент React в состоянии загрузки. Следующий пример показывает, как это работает. Аватар отображается только при нажатии кнопки, после чего делается запрос на получение кода, необходимого для приостановленного AvatarComponent . Тем временем отображается резервный компонент загрузки.

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

Чтобы лучше продемонстрировать, как это работает:

  • Чтобы просмотреть сайт, нажмите «Просмотреть приложение» . Затем нажмите Полноэкранный режим полноэкранный .
  • Нажмите «Control+Shift+J» (или «Command+Option+J» на Mac), чтобы открыть DevTools.
  • Откройте вкладку «Сеть» .
  • Щелкните раскрывающийся список «Регулирование» , для которого по умолчанию установлено значение «Без регулирования» . Выберите Быстрый 3G .
  • Нажмите кнопку «Нажми на меня» в приложении.

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

Сетевая панель DevTools, показывающая загружаемый один файл chunk.js

Приостановка нескольких компонентов

Еще одна особенность Suspense заключается в том, что она позволяет приостанавливать загрузку нескольких компонентов, даже если все они загружаются отложенно .

Например:

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

Это чрезвычайно полезный способ задержать рендеринг нескольких компонентов, отображая при этом только одно состояние загрузки. Как только все компоненты завершат выборку, пользователь увидит их все одновременно.

Вы можете увидеть это с помощью следующей вставки:

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

Обработка сбоев при загрузке

Suspense позволяет отображать временное состояние загрузки, пока сетевые запросы выполняются скрытно. Но что, если эти сетевые запросы по какой-то причине завершатся неудачей? Возможно, вы находитесь в автономном режиме или ваше веб-приложение пытается отложенно загрузить URL-адрес с версией , которая устарела и больше не доступна после повторного развертывания сервера.

В React есть стандартный шаблон для корректной обработки подобных ошибок загрузки: использование границы ошибки. Как описано в документации , любой компонент React может служить границей ошибки, если он реализует один (или оба) метода жизненного цикла static getDerivedStateFromError() или componentDidCatch() .

Чтобы обнаружить и обработать сбои отложенной загрузки, вы можете обернуть компонент Suspense родительскими компонентами, которые служат границей ошибки. Внутри метода render() границы ошибки вы можете отображать дочерние элементы как есть, если ошибок нет, или отображать собственное сообщение об ошибке, если что-то пойдет не так:

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

Заключение

Если вы не знаете, с чего начать применение разделения кода в вашем приложении React, выполните следующие действия:

  1. Начните с уровня маршрута. Маршруты — это самый простой способ определить точки вашего приложения, которые можно разделить. В документации React показано, как можно использовать Suspense вместе с react-router .
  2. Определите любые крупные компоненты на странице вашего сайта, которые отображаются только при определенных взаимодействиях с пользователем (например, при нажатии кнопки). Разделение этих компонентов сведет к минимуму полезную нагрузку JavaScript.
  3. Рассмотрите возможность разделения всего остального, что находится за кадром и не критично для пользователя.