Code splitting
1. Code splitting
Когда приложение становится достаточно большим, монолитный бандл со всем исходным кодом загружается, парсится и исполняется, долго (секунды, особенно на мобильных). Но если пользователь заходит на страницу логина, не имеет смысла загружать остальную часть приложения.
Секция приложения может импортировать большое количество компонентов, которые не нужны при первой загрузке. Необходимо разбивать бандл на отдельные файлы (chunks, чанки) и загружать их динамически, только когда это необходимо.

1.1. Динамический импорт в ESM
// Статический импорт, всегда в начале файла
import moduleA from './path/to/module-a';
/*
* Динамический импорт, где угодно
* Путь к модулю должен быть полный, без выражений
*/
const loadModuleB = () => import('./path/to/module-b');
/*
* Можно использовать в любом месте, к примеру при событии
* import() возвращает обещание
*/
loadModuleB().then(module => {
// Объект модуля с полем default - дефолтный экспорт
console.log(module);
});
Create React App поддерживает разделение кода и позволяет динамически импортировать части приложения используя import()
.
2. Route-based и Component-based splitting
Первый подход состоит в разбиении приложения на отдельные маршруты и загрузке каждого маршрута асинхронно. Этого вполне достаточно для большинства приложений, переходим на новый раут - загружается необходимый код для его обслуживания.
Но есть еще один вариант. Раут это просто компонент. Поэтому можно разделять код на уровне компонентов, а не маршрутов. Какая в этом выгода?
В действительно большом приложении - огромная. Интерфейсы могут быть очень громоздкими, и в тех местах, где модальные окна, вкладки, скрытые формы и т. п., не нужны до определенного действия пользователя, разделение кода помогает ускорить рендер страницы.
Это не значит что необходимо использовать один или другой подход, используйте оба. Делите и маршруты, и части страницы, это все просто компоненты. Как, что и где разделять это целиком ваше решение, зависящее от сложившейся ситуации.
Чрезмерный сплитинг, кстати, тоже не лучшая идея. HTTP-запрос за файлом может быть дольше чем добавленный вес к первой загрузке.
2.1. asyncComponent HOC
Функция
asyncComponent
принимает объект настроек из двух свойств:loader
- функция, которая при вызове будет динамически импортировать компонент,loading
- компонент который будем показывать пока идет HTTP-запрос.В
componentDidMount
мы просто вызываем функциюloader
, и сохраняем динамически загруженный компонент в состояние.После чего делаем рендер по условию, где рендерим загруженный компонент, если он уже загрузился. В противном случае рендерим компонент
Loading
.
// asyncComponent.js
const asyncComponent = ({ loader, loading: Loading }) => {
return class AsyncComponent extends Component {
state = {
component: null,
};
async componentDidMount() {
const { default: component } = await loader();
this.setState({ component });
}
render() {
const { component: LoadedComponent } = this.state;
return LoadedComponent ? (
<LoadedComponent {...this.props} />
) : (
<Loading />
);
}
};
};
Теперь используем эту функцию для динамической загрузки страниц.
const AsyncHome = asyncComponent({
loader: () => import('./pages/Home'),
loading: Loader,
});
Важно понимать, что здесь не происходит импорта компонента. Мы передаем функцию которая будет использована для динамического импорта, когда будет создан компонент AsyncHome
.
Может показаться странным, что мы передаем функцию. Почему бы просто не передать строку './pages/Home'
, а затем выполнить динамический импорт внутри asyncComponent
?
Это связано с тем, что мы хотим явно указать компонент, который мы динамически импортируем. Основываясь на этом, Webpack разбивает наше приложение. Он смотрит на эти импорты и генерирует необходимые чанки (куски, chunks).
После создания AsyncHome
мы можем использовать его в маршрутах. При совпадении маршрута, React Router создаст экземпляр компонента AsyncHome
, который, в свою очередь, динамически импортирует компонент Home
и отрендерит его.
<Route path="/" exact component={AsyncHome} />

Каждый из файлов .chunk.js
- это вызовы import()
, асинхронно загружаемые компоненты.
2.2. Имена чанков
Если необходимо дать чанкам вменяемые имена, делается это так:
const AsyncHome = asyncComponent({
loader: () => import('./pages/Home' /* webpackChunkName: "home-page" */),
loading: Loader,
});
code-splitting-specify-chunk-name
3. React Loadable
Что произойдет, если запрос на импорт нового компонента займет слишком много времени или HTTP-запрос не удастся. Или, возможно, необходимо предварительно загрузить определенные компоненты. Например, пользователь находится странице логина, и необходимо предварительно загрузить домашнюю страницу.
React Loadable
- небольшая библиотека, которая упрощает разделение компонентно-ориентированного кода.
Loadable
- компонент высшего порядка, который позволяет легко разделить код на уровне компонентов.
Добавим пакет в проект.
npm install react-loadable
Достаточно заменить asyncComponent
на Loadable
и все продолжит работать как и раньше.
Одно но, можно улучшить компонент Loading
, потому что react-loadable
прокидывает ему несколько очень полезных свойств.
const Loading = ({ error, timedOut, pastDelay, retry }) => {
if (error) {
return (
<div>
Error! <button onClick={retry}>Retry</button>
</div>
);
}
if (timedOut) {
return (
<div>
Taking a long time... <button onClick={retry}>Retry</button>
</div>
);
}
if (pastDelay) {
return <div>Loading...</div>;
}
return null;
};
4. React.lazy версии 16.6+
Официальный API который позволяет рендерить динамически загружаемый компонент. Заменит react-loadable
.
React.lazy(loader);
Принимает функцию-згарузчик которая возвращает результат динамического импорта - промис, значение которого будет дефолтный экспорт модуля, реакт компонент.
import React, { lazy, Suspense } from 'react';
const AsyncComponent = lazy(() => import('./AsyncComponent'));
const MyComponent = () => (
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
);
Если во время ренедера MyComponent
, компонент AsyncComponent
еще не загружен, необходимо показать фолбек. Для этого используется компонент Suspense
. Проп fallback
принимает любой React-элемент. Susupense
можно поместить в любом месте над асинхронным компонентом, даже оборачивая целую группу.
5. Дополнительные материалы
Last updated
Was this helpful?