Ленивая загрузка (lazy loading) — это техника оптимизации интерфейса, при которой ресурсы, данные или компоненты загружаются только в момент фактической необходимости. В контексте пользовательских интерфейсов она уменьшает первоначальный объём загрузки, ускоряет отображение страницы и снижает нагрузку на сеть и процессор.
В современных веб-приложениях объём JavaScript и количество компонентов постоянно растут. Без оптимизаций это приводит к увеличению времени загрузки и ухудшению пользовательского опыта. Ленивая загрузка решает эту проблему за счёт:
В экосистеме React и библиотеки React Aria ленивые стратегии особенно важны, поскольку компоненты часто имеют сложную внутреннюю логику доступности, обработчики клавиатуры и управление состоянием. Грамотная ленивная загрузка позволяет сохранить высокую производительность интерфейса без потери доступности.
React поддерживает ленивую загрузку компонентов через механизм динамического импорта. Вместо загрузки всех модулей при инициализации приложения компонент загружается только тогда, когда он впервые используется.
Базовый механизм реализуется через React.lazy.
import React, { Suspense } from "react";
const Dialog = React.lazy(() => import("./Dialog"));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<Dialog />
</Suspense>
);
}
Ключевые элементы механизма:
| Элемент | Назначение |
|---|---|
React.lazy |
лениво загружает компонент |
import() |
динамический импорт модуля |
Suspense |
отображает резервный интерфейс во время загрузки |
В результате компонент Dialog загружается только в
момент рендеринга.
Компоненты React Aria часто используются внутри более крупных UI-структур — модальных окон, меню, выпадающих списков и сложных форм. Эти элементы редко отображаются сразу после загрузки страницы, поэтому их загрузка может быть отложена.
Пример ленивой загрузки меню.
const Menu = React.lazy(() => import("./Menu"));
function Toolbar() {
const [open, setOpen] = React.useState(false);
return (
<>
<button onCl ick={() => setOpen(true)}>
Открыть меню
</button>
{open && (
<Suspense fallback={<div>Загрузка меню...</div>}>
<Menu />
</Suspense>
)}
</>
);
}
Преимущества такого подхода:
Многие компоненты React Aria работают с динамическими наборами данных: списками, таблицами, выпадающими меню. Загрузка всей коллекции данных сразу может привести к серьёзным проблемам производительности.
Поэтому используется инкрементальная загрузка данных.
React Aria предоставляет инструменты для работы с асинхронными источниками данных через библиотеку React Stately.
Одним из ключевых инструментов является
useAsyncList.
import {useAsyncList} from "@react-stately/data";
function useUsers() {
return useAsyncList({
async load({signal, cursor}) {
const res = await fetch(cursor || "/api/users", {signal});
const json = await res.json();
return {
items: json.items,
cursor: json.nextCursor
};
}
});
}
Особенности:
AbortControllerКомпоненты React Aria, такие как списки и коллекции, могут работать с асинхронными источниками данных.
import {useListBox, useOption} from "react-aria";
function UserList() {
const list = useUsers();
if (list.isLoading) {
return <div>Загрузка...</div>;
}
return (
<ul>
{list.items.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
);
}
Данные подгружаются по мере необходимости.
Infinite scrolling — это техника, при которой новые элементы списка подгружаются автоматически при прокрутке интерфейса.
Она особенно полезна для:
В сочетании с React Aria этот подход должен сохранять доступность для пользователей клавиатуры и экранных читателей.
Для обнаружения конца списка используется Intersection Observer.
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
loadMore();
}
});
Элемент-триггер помещается в конец списка.
<div ref={loaderRef}>
Загрузка...
</div>
Когда он появляется в области видимости, запускается загрузка следующей порции данных.
При работе с очень большими коллекциями (тысячи элементов) даже ленивой загрузки недостаточно. Основная проблема — рендеринг большого количества DOM-элементов.
Решением является виртуализация.
Виртуализация отображает только те элементы, которые находятся в видимой области.
Основные принципы:
Виртуализированный список состоит из нескольких слоёв:
Контейнер прокрутки
<div className="scroll-container">
Внутренний контейнер высоты
<div style={{height: totalHeight}}>
Отображаемые элементы
items.slice(start, end)
В результате:
Интерфейсы часто содержат большое количество изображений. Их загрузка может занимать значительную часть сетевого трафика.
HTML поддерживает встроенную ленивую загрузку.
<img src="image.jpg" loading="lazy" alt="..." />
Однако в React-интерфейсах часто используется более гибкая логика.
function LazyImage({src, alt}) {
const ref = React.useRef();
const [visible, setVisible] = React.useState(false);
React.useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
}
});
observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<img
ref={ref}
src={visible ? src : ""}
alt={alt}
/>
);
}
Изображение загружается только после попадания в область видимости.
Модальные окна, диалоги и всплывающие панели используются эпизодически. Их загрузка при старте приложения часто не имеет смысла.
Компоненты React Aria для диалогов могут загружаться динамически.
const Dialog = React.lazy(() => import("./Dialog"));
function Page() {
const [open, setOpen] = React.useState(false);
return (
<>
<button onCl ick={() => setOpen(true)}>
Открыть
</button>
{open && (
<Suspense fallback={<div>Загрузка...</div>}>
<Dialog />
</Suspense>
)}
</>
);
}
Такой подход особенно эффективен, когда модальный компонент содержит:
Во время ленивой загрузки важно избегать резких изменений интерфейса.
Для этого используются skeleton screens — временные визуальные заглушки.
Пример skeleton-карточки.
function CardSkeleton() {
return (
<div className="skeleton-card">
<div className="skeleton-image"></div>
<div className="skeleton-text"></div>
<div className="skeleton-text short"></div>
</div>
);
}
Skeleton создаёт иллюзию постепенной загрузки интерфейса.
В React-приложениях обычно используется несколько состояний загрузки:
| Состояние | Назначение |
|---|---|
loading |
первичная загрузка |
loadingMore |
подгрузка элементов |
error |
ошибка запроса |
idle |
данные загружены |
Пример реализации.
if (list.isLoading) {
return <Spinner />;
}
if (list.error) {
return <ErrorMessage />;
}
Это позволяет управлять интерфейсом при асинхронной загрузке.
Некорректная ленивная загрузка может нарушить доступность интерфейса.
Распространённые проблемы:
Для решения используются:
aria-liveСлишком большое количество лениво загружаемых модулей может привести к:
Баланс между размером бандла и количеством чанков должен поддерживаться на уровне архитектуры приложения.
На практике используются несколько стратегий ленивой загрузки.
Каждая страница приложения загружается отдельным модулем.
Компоненты загружаются при клике или открытии меню.
Контент загружается при прокрутке страницы.
Некоторые модули можно загружать заранее.
import("./Dialog");
Это уменьшает задержку при последующем открытии.
Использование ленивой загрузки оправдано, если:
Не рекомендуется применять ленивую загрузку, если:
Грамотное сочетание ленивой загрузки компонентов, асинхронных коллекций и виртуализации позволяет создавать интерфейсы React Aria, способные эффективно работать даже с очень большими объёмами данных и сложными структурами интерфейса.