3.14 Разбиение кода

3.14.1 Сборка


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

Пример:

App:


Код
    
  // 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.



3.14.2 Разбиение кода


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

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

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



3.14.3 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 в состоянии анализировать синтаксис динамического импорта и не преобразовывать его. Для этого вам понадобится babel-plugin-syntax-dynamic-import.



3.14.4 React.lazy



Внимание!

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

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

До:


Код
    
  import OtherComponent from './OtherComponent';
  
  function MyComponent() {
    return (
      <div>
        <OtherComponent />
      </div>
    );
  }
  

После:


Код
    
  const OtherComponent = React.lazy(() => import('./OtherComponent'));
  
  function MyComponent() {
    return (
      <div>
        <OtherComponent />
      </div>
    );
  }
  


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

React.lazy принимает функцию, которая должна вызывать динамический импорт: import(). Он должен вернуть Promise, который разрешается в модуль с default экспортом компонента React.



3.14.4.1 Приостановка


Если модуль, содержащий OtherComponent, еще не загружен к моменту отрисовки MyComponent, мы должны показать некий резервный контент во время ожидания - например, индикатор загрузки. Это делается с помощью компонента Suspense.



Код
    
  const OtherComponent = React.lazy(() => import('./OtherComponent'));
  
  function MyComponent() {
    return (
      <div>
        <Suspense fallback={<div>Loading...</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>Loading...</div>}>
          <section>
            <OtherComponent />
            <AnotherComponent />
          </section>
        </Suspense>
      </div>
        );
  }
  



3.14.4.2 Границы ошибок


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



Код
    
  import MyErrorBoundary from './MyErrorBoundary';
  const OtherComponent = React.lazy(() => import('./OtherComponent'));
  const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
  
  const MyComponent = () => (
    <div>
      <MyErrorBoundary>
        <Suspense fallback={<div>Loading...</div>}>
          <section>
            <OtherComponent />
            <AnotherComponent />
          </section>
        </Suspense>
      </MyErrorBoundary>
    </div>
   );
  



3.14.4.3 Разбиение кода, основанное на маршрутах


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

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

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



Код
    
  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>Loading...</div>}>
        <Switch>
          <Route exact path="/" component={Home}/>
          <Route path="/about" component={About}/>
        </Switch>
      </Suspense>
    </Router>
  );
  



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


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



Код
    
  // 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"));