Перейти до основного вмісту

Розділення коду

За замовчуванням, всі залежності проекту об'єднуються в один файл. Чим більше коду, тим повільніше він завантажуватиметься, парситься і виконуватиметься у браузері користувача. На слабких комп'ютерах або телефонах, з поганим підключенням до Інтернет може бути десятки секунд.

При розробці на локальному сервері (localhost) усі файли раздаються з нашого комп'ютера. У цьому випадку швидкість підключення до Інтернету не має значення, та тому файли проекту завантажуються дуже швидко. Однак у продакшені завантаження великих файлів може стати проблемою, тому що не скрізь є високошвидкісний інтернет та потужні комп'ютери.

Вирішення проблеми очевидне - розбити проект на дрібніші файли та завантажувати їх лише за потребою. У цьому полягає концепція поділу коду. Якщо користувач заходить на сторінку логіна, не потрібно завантажувати весь код програми, буде достатньо частини, що відповідає за рендер компонентів тільки цієї сторінки.

Code splitting
Create React App

Поділ коду на кілька файлів це завдання збирача проекту, наприклад Webpack, а не фронтенд фреймворку. Create React App внутрішньо використовує Webpack як збирач і підтримує поділ коду без додаткового налаштування.

Код програми необхідно розділяти за маршрутами та завантажувати за потребою. Цього достатньо для більшості програм. Переходимо на нову сторінку - завантажується необхідний код відображення її компонентів. Такий підхід називається поділ коду на основі маршрутів (route-centric).

Route-centric code splitting

Інтерфейси можуть бути дуже громіздкими. Якщо піти далі, то можна оптимізувати завантаження окремих, дуже великих компонентів сторінки, які не потрібні до певної дії користувача. Наприклад, компонент модального вікна, в якому використовується велика бібліотека текстового редактора. Такий підхід називається поділ коду на основі компонентів (component-centric).

Component-centric code splitting
Що використовувати?

Розробник приймає рішення як, що і де поділяти. Проте ось кілька найкращих практик.

  • Поділ коду на основі маршрутів є обов'язковим у будь-якому додатку.
  • Поділ коду на основі компонентів варто робити тільки у великих, складних інтерфейсах із сотнями компонентів та великими бібліотеками.
  • Надмірний поділ коду також не найкраща ідея. HTTP-запит за файлом може бути довше ніж додана вага до першого завантаження.

React.lazy() та React.Suspense

Ви вже знаєте що ES модулі статичні, тобто імпорти та експорти виконуються у час компіляції, а не під час виконання коду. Імпорти повинні бути оголошені у верхній частині файлу, інакше буде помилка компіляції. Це означає, що ви не можете імпортувати залежність динамічно на основі якоїсь умови.

Без поділу коду
import MyComponent from "path/to/MyComponent";

const App = () => {
return (
<Routes>
<Route path="/some-path" element={<MyComponent />} />
{/* Інші маршрути */}
</Routes>
);
};

У специфікації ES2020 з'явилася можливість динамічного імпортування модуля. Різниця в тому, що замість звичайного статичного import використовується функція import(), яка повертає проміс, значенням якого буде файл модуля.

import("path/to/MyComponent").then(module => console.log(module));

React надає API для того, щоб вказати який код необхідно виділити в окремий файл, а потім завантажувати та рендерувати лише за потреби. Функція React.lazy() відповідає за асинхронне завантаження компонента, а Suspense призупиняє його відображення до завершення завантаження.

З поділом коду
import { lazy, Suspense } from "react";

const MyComponent = lazy(() => import("path/to/MyComponent"));

const App = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/some-path" element={<MyComponent />} />
{/* Інші маршрути */}
</Routes>
</Suspense>
);
};

Метод lazy() очікує функцію-завантажувач, яка повертає результат динамічного імпорту - проміс, значенням якого буде дефолтний експорт модуль (компонент). Якщо під час рендеру компонент MyComponent ще не завантажений, потрібно показати заглушку. Для цього використовується компонент Suspense. Проп fallback приймає будь-який React-елемент або компонент. Suspense можна помістити будь-де над асинхронним компонентом або групою компонентів.

Динамічний імпорт

Зверніть увагу на відсутність статичного імпорту MyComponent в останньому прикладі. Натомість використовується функція import(). Якщо залишити статичний імпорт, то Webpack не виконає поділ коду і додасть весь код MyComponent в основний JavaScript файл проекту.

Suspense та прийом «shared layout»

Якщо ви використовуєте прийом «shared layout», то потрібно розмістити Suspense безпосередньо всередині компонента SharedLayout. В іншому випадку, при завантаженні кожної сторінки, будуть пропадати і повторно рендерувати компоненти загальної частини сторінок, наприклад хедер та навігація.

// src/components/App.jsx
import { lazy } from "react";

const MyComponent = lazy(() => import("path/to/MyComponent"));

const App = () => {
return (
<Routes>
<Route path="/" element={<SharedLayout />}>
<Route path="some-path" element={<MyComponent />} />
{/* Інші маршрути */}
</Route>
</Routes>
);
};

// src/components/SharedLayout.jsx
import { Suspense } from "react";
import { Outlet } from "react-router-dom";

const SharedLayout = () => {
return (
<Container>
<AppBar>
<Navigation />
<UserMenu />
</AppBar>

<Suspense fallback={<div>Loading...</div>}>
<Outlet />
</Suspense>
</Container>
);
};

Розберіть повний код програми магазину з розділенням коду на основі маршрутів. Змінився код компонентів App, SharedLayout та About, а всі компоненти сторінок стали дефолтними експортами.

Вкладені маршрути та Suspense

Зверніть увагу на використання компонента Suspense у коді компонента сторінки About. Так, при завантаженні підсторінок, не заново малюватиметься вся сторінка, лише її нижня частина з розміткою підсторінок. Компоненти Suspense у SharedLayout та About не заважають один одному, натомість React використовує найбільш підходящий - той, що найближче до завантажуваного компоненту.