Разделение кода

Бандлинг

Большинство React-приложений «собирают» свои файлы такими инструментами, как Webpack, Rollup или Browserify. Сборка (или «бандлинг») — это процесс выявления импортированных файлов и объединения их в один «собранный» файл (часто называемый «bundle» или «бандл»). Этот бандл после подключения на веб-страницу загружает всё приложение за один раз.

Пример

Приложение:

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

Бандл:

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

Примечание:

Ваши бандлы будут выглядеть не так, как мы только что показали.

Если вы используете Create React App, Next.js, Gatsby или похожие инструменты, то у вас уже будет настроенный Webpack для бандлинга приложения.

Иначе, вам нужно будет настроить webpack самостоятельно. Для этого ознакомьтесь со страницами Установка и Начало работы в документации по Webpack.

Разделение кода

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

Чтобы предотвратить разрастание бандла, стоит начать «разделять» ваш бандл. Разделение кода — это возможность, поддерживаемая такими бандлерами как Webpack, Rollup или Browserify (с factor-bundle), которая может создавать несколько бандлов и загружать их по мере необходимости.

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

import()

Лучший способ внедрить разделение кода в приложение — использовать синтаксис динамического импорта: import().

До:

import { add } from './math';

console.log(add(16, 26));

После:

import("./math").then(math => {
  console.log(math.add(16, 26));
});

Примечание:

Синтаксис динамического импорта import() — это ECMAScript (JavaScript) предложение, которое в данный момент не входит в стандарт языка. Ожидается, что он будет принят в ближайшем будущем.

Когда Webpack сталкивается с таким синтаксисом, он автоматически начинает разделять код вашего приложения. Если вы используете Create React App, то всё уже настроено и вы можете сразу начать использовать синтаксис динамического импорта. Он также поддерживается «из коробки» в Next.js.

Если вы настраиваете Webpack самостоятельно, то, вероятно, захотите прочитать руководство Webpack по разделению кода. Файл конфигурации Webpack должен выглядеть примерно так.

Если вы используете Babel, вам необходимо убедиться, что он понимает синтаксис динамического импорта. Для этого вам необходимо установить плагин @babel/plugin-syntax-dynamic-import.

React.lazy

Примечание:

Возможности React.lazy и задержки (suspense) пока недоступны для рендеринга на стороне сервера. Если вам нужно разделение кода в серверном приложении, мы рекомендуем Loadable Components. У них есть хорошее руководство по разделению бандла с серверным рендерингом.

Функция React.lazy позволяет рендерить динамический импорт как обычный компонент.

До:

import OtherComponent from './OtherComponent';

После:

const OtherComponent = React.lazy(() => import('./OtherComponent'));

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

React.lazy принимает функцию, которая должна вызвать динамический import(). Результатом возвращённого Promise является модуль, который экспортирует по умолчанию React-компонент (export default).

Задержка

Компонент с ленивой загрузкой должен рендериться внутри компонента Suspense, который позволяет нам показать запасное содержимое (например, индикатор загрузки) пока происходит загрузка ленивого компонента.

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Загрузка...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

Проп fallback принимает любой React-элемент, который вы хотите показать, пока происходит загрузка компонента. Компонент Suspense можно разместить в любом месте над ленивым компонентом. Кроме того, можно обернуть несколько ленивых компонентов одним компонентом Suspense.

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Загрузка...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Предохранители

Если какой-то модуль не загружается (например, из-за сбоя сети), это вызовет ошибку. Вы можете обрабатывать эти ошибки для улучшения пользовательского опыта с помощью Предохранителей. После создания предохранителя, его можно использовать в любом месте над ленивыми компонентами для отображения состояния ошибки.

import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Загрузка...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

Разделение кода на основе маршрутов

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

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

Вот пример того, как организовать разделение кода на основе маршрутов с помощью React.lazy и таких библиотек как React Router.

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Загрузка...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Именованный экспорт

React.lazy в настоящее время поддерживает только экспорт по умолчанию. Если модуль, который требуется импортировать, использует именованный экспорт, можно создать промежуточный модуль, который повторно экспортирует его как модуль по умолчанию. Это гарантирует работоспособность tree shaking — механизма устранения неиспользуемого кода.

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));