Пошук уроків, статей та іншого контенту
Налаштуємо динамічний імпорт і відкладене завантаження компонентів у потрібний момент.
Lazy loading — це відкладене завантаження коду лише в той момент, коли він справді потрібен.
Без lazy loading усі компоненти можуть потрапити в один початковий JavaScript-файл. Чим більший файл, тим довше браузер завантажує та обробляє застосунок.
Замість цього компонент можна завантажити динамічно:
Початково завантажується лише код, необхідний для першого екрана.
Користувач виконує дію або відкриває потрібний розділ.
React завантажує код компонента.
Поки завантаження триває, відображається запасний інтерфейс.
У React для цього використовують:
lazy — створює компонент із динамічного імпорту;
Suspense — показує запасний інтерфейс під час завантаження.
Звичайний статичний імпорт виконується під час завантаження модуля:
import Profile from './Profile';Такий компонент потрапляє до початкового набору JavaScript-коду.
Динамічний імпорт повертає Promise:
const profileModule = import('./Profile');Файл Profile буде завантажено окремо, коли виконається import().
React може використовувати цей механізм через lazy:
import { lazy } from 'react';
const Profile = lazy(() => import('./Profile'));Тепер Profile завантажується не під час початкового імпорту App, а тоді, коли React спробує відобразити цей компонент.
SuspenseПоки динамічний імпорт виконується, компонент ще недоступний для відображення. Тому lazy потрібно використовувати всередині Suspense.
import { Suspense, lazy } from 'react';
const Profile = lazy(() => import('./Profile'));
function App() {
return (
<Suspense fallback={<p>Завантаження профілю...</p>}>
<Profile />
</Suspense>
);
}Властивість fallback містить інтерфейс, який React показує під час завантаження компонента.
У fallback можна передати:
текст;
індикатор завантаження;
скелетон;
окремий компонент.
<Suspense fallback={<Loading />}>
<Profile />
</Suspense>Розглянемо застосунок із двома розділами. Головна сторінка завантажується одразу, а профіль — лише після натискання кнопки.
Profile.jsxexport default function Profile() {
return (
<section>
<h2>Профіль користувача</h2>
<p>Цей компонент було завантажено динамічно.</p>
</section>
);
}App.jsximport { lazy, Suspense, useState } from 'react';
const Profile = lazy(() => import('./Profile'));
function Home() {
return (
<section>
<h2>Головна сторінка</h2>
<p>Цей компонент входить до початкового коду застосунку.</p>
</section>
);
}
function Loading() {
return <p>Завантаження розділу...</p>;
}
export default function App() {
const [page, setPage] = useState('home');
return (
<main>
<h1>Мій застосунок</h1>
<nav>
<button type="button" onClick={() => setPage('home')}>
Головна
</button>
<button type="button" onClick={() => setPage('profile')}>
Профіль
</button>
</nav>
<Suspense fallback={<Loading />}>
{page === 'home' && <Home />}
{page === 'profile' && <Profile />}
</Suspense>
</main>
);
}Коли застосунок відкривається, React відображає Home. Код Profile ще не потрібен.
Після натискання кнопки «Профіль»:
React намагається відобразити Profile;
виконується import('./Profile');
браузер завантажує окремий файл;
до завершення завантаження Suspense показує Loading;
після завантаження React відображає Profile.
SuspenseSuspense може обгортати один компонент або цілу частину дерева:
<Suspense fallback={<p>Завантаження...</p>}>
<Profile />
<Settings />
</Suspense>У такому випадку запасний інтерфейс буде показано, якщо будь-який із компонентів усередині межі очікує завантаження.
Також можна створити кілька незалежних меж:
<Suspense fallback={<p>Завантаження профілю...</p>}>
<Profile />
</Suspense>
<Suspense fallback={<p>Завантаження налаштувань...</p>}>
<Settings />
</Suspense>Це дає змогу завантажувати частини інтерфейсу незалежно одна від одної. Межу варто розташовувати там, де користувачеві зрозуміло, яка саме частина інтерфейсу ще завантажується.
Відкладене завантаження особливо корисне для компонентів, які користувач відкриває не завжди: модальних вікон, редакторів, складних форм або додаткових панелей.
HelpModal.jsxexport default function HelpModal({ onClose }) {
return (
<div role="dialog" aria-modal="true">
<h2>Довідка</h2>
<p>Цей компонент завантажується лише після відкриття вікна.</p>
<button type="button" onClick={onClose}>
Закрити
</button>
</div>
);
}App.jsximport { lazy, Suspense, useState } from 'react';
const HelpModal = lazy(() => import('./HelpModal'));
export default function App() {
const [isHelpOpen, setIsHelpOpen] = useState(false);
return (
<main>
<h1>Сторінка документації</h1>
<button type="button" onClick={() => setIsHelpOpen(true)}>
Відкрити довідку
</button>
{isHelpOpen && (
<Suspense fallback={<p>Завантаження довідки...</p>}>
<HelpModal onClose={() => setIsHelpOpen(false)} />
</Suspense>
)}
</main>
);
}У цьому прикладі код модального вікна не завантажується для користувача, який ніколи не відкриває довідку.
React.lazy очікує, що динамічно імпортований модуль матиме default-експорт:
export default function Profile() {
return <h2>Профіль</h2>;
}Якщо компонент експортується іменовано:
export function Profile() {
return <h2>Профіль</h2>;
}його можна адаптувати до формату default:
import { lazy } from 'react';
const Profile = lazy(() =>
import('./Profile').then((module) => ({
default: module.Profile,
})),
);Проте для компонентів, які потрібно завантажувати через lazy, зазвичай простіше використовувати default-експорт.
Suspense обробляє стан очікування, але не помилки завантаження. Наприклад, динамічний імпорт може завершитися помилкою через відсутність мережі.
Для обробки таких помилок використовують Error Boundary.
import { Component, lazy, Suspense } from 'react';
const Profile = lazy(() => import('./Profile'));
class ErrorBoundary extends Component {
state = {
hasError: false,
};
static getDerivedStateFromError() {
return {
hasError: true,
};
}
render() {
if (this.state.hasError) {
return <p>Не вдалося завантажити цей розділ.</p>;
}
return this.props.children;
}
}
export default function App() {
return (
<ErrorBoundary>
<Suspense fallback={<p>Завантаження профілю...</p>}>
<Profile />
</Suspense>
</ErrorBoundary>
);
}У реальному застосунку повідомлення про помилку може також містити кнопку повторної спроби або інструкцію перевірити з’єднання.
Lazy loading доречний, коли компонент:
не потрібен для першого екрана;
відкривається після дії користувача;
містить багато коду;
використовується лише в окремих розділах застосунку;
містить важкі залежності або складний інтерфейс.
Не кожен маленький компонент потрібно завантажувати окремо. Надмірне розділення коду може створити багато дрібних мережевих запитів і ускладнити застосунок.
Головна мета — не відкласти завантаження всього підряд, а прибрати з початкового завантаження код, який не потрібен користувачеві одразу.
lazy без SuspenseНеправильно:
const Profile = lazy(() => import('./Profile'));
function App() {
return <Profile />;
}Правильно:
<Suspense fallback={<p>Завантаження...</p>}>
<Profile />
</Suspense>Шлях у динамічному імпорті має вказувати на файл відносно поточного модуля:
const Profile = lazy(() => import('./Profile'));Якщо файл розташований в іншій директорії, шлях потрібно змінити відповідно:
const Profile = lazy(() => import('./components/Profile'));lazyЯкщо lazy очікує default-експорт, а модуль містить лише іменований експорт, компонент не завантажиться без адаптації.
Перевірте, що файл містить:
export default Profile;або використайте перетворення через .then().
Не створюйте lazy всередині компонента:
function App() {
const Profile = lazy(() => import('./Profile'));
return <Profile />;
}Так компонент створюється заново під час кожного рендерингу.
Створюйте його на рівні модуля:
const Profile = lazy(() => import('./Profile'));
function App() {
return <Profile />;
}fallbackПорожній запасний інтерфейс:
<Suspense fallback={null}>
<Profile />
</Suspense>може створити враження, що застосунок перестав працювати. Краще показати коротке зрозуміле повідомлення або візуальний індикатор завантаження.
Динамічний import() завантажує модуль окремо та лише за потреби.
lazy(() => import(...)) перетворює динамічний імпорт на React-компонент.
lazy потрібно використовувати всередині Suspense.
fallback визначає інтерфейс під час завантаження.
Компоненти з default-експортом працюють із React.lazy без додаткових перетворень.
Error Boundary потрібен для обробки помилок динамічного завантаження.
Lazy loading найкраще застосовувати до великих або рідко використовуваних частин інтерфейсу.