Что такое lazy и Suspense в React?

MiddleReact · Frontend·Обновлено 2 июля 2026
Коротко
React.lazy позволяет загружать компоненты динамически (code splitting), а Suspense — отображать fallback-интерфейс до того, как ленивый компонент или другой асинхронный ресурс будет готов.

React.lazy и Suspense

React.lazy и Suspense — два взаимосвязанных механизма, позволяющих реализовать ленивую загрузку компонентов и управлять состоянием ожидания асинхронных данных.

React.lazy

React.lazy принимает функцию, которая возвращает динамический import(). Это позволяет браузеру загрузить JS-бандл компонента только тогда, когда он действительно нужен — не при старте приложения, а при первом рендере.

const HeavyChart = React.lazy(() => import('./HeavyChart'));

Под капотом React.lazy создаёт специальный объект-обёртку. При первом рендере React видит, что загрузка ещё не завершена, и «приостанавливает» рендер, бросая специальный Promise — именно его перехватывает ближайший <Suspense>.

Suspense

<Suspense fallback={...}> — это граница ожидания. Пока дочерние компоненты «приостановлены», Suspense показывает fallback. Как только загрузка завершается, fallback убирается и отрисовывается реальный контент.

<Suspense fallback={<Spinner />}>
  <HeavyChart data={data} />
</Suspense>

Зачем это нужно

  • Уменьшение размера начального бандла — пользователь быстрее получает первый экран (улучшается FCP и TTI).
  • Разделение по маршрутам — каждая страница загружается только при переходе на неё.
  • Приоритизация загрузки — критичный UI грузится сразу, второстепенный — по требованию.

Использование с React Router

const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Settings = React.lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Suspense для данных (React 18+)

Начиная с React 18, Suspense работает не только с lazy, но и с асинхронными источниками данных через библиотеки (React Query, Relay, SWR) или нативный use() hook:

// use() приостанавливает рендер, если Promise ещё не resolved
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

Важные ограничения

  • React.lazy работает только с default export.
  • Suspense не перехватывает ошибки загрузки — для этого нужен отдельный Error Boundary.
  • На сервере (SSR без специальной поддержки) React.lazy не работает — для Next.js используют next/dynamic.

Вложенные границы Suspense

Можно вкладывать несколько Suspense для разной гранулярности:

<Suspense fallback={<PageSkeleton />}>
  <Header />
  <Suspense fallback={<ChartSkeleton />}>
    <HeavyChart />
  </Suspense>
</Suspense>

Внутренняя граница перехватывает приостановку HeavyChart, не блокируя рендер остальной страницы.

Что хочет услышать интервьюер

Понимание механизма code splitting и зачем он нужен для производительности

Объяснение связки lazy + Suspense: lazy бросает Promise, Suspense его перехватывает и показывает fallback

Знание ограничений: только default export, нужен Error Boundary для обработки ошибок

Практический пример — разбивка по маршрутам с React Router

Понимание расширенных возможностей Suspense в React 18 (данные, use())

Пример: Базовое использование React.lazy + Suspense

import React, { Suspense } from 'react';

// Компонент загрузится только при первом рендере
const HeavyChart = React.lazy(() => import('./HeavyChart'));

function Dashboard() {
  return (
    <Suspense fallback={<div>Загружаем график...</div>}>
      <HeavyChart />
    </Suspense>
  );
}

Пример: Code splitting по маршрутам + Error Boundary

import React, { Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { ErrorBoundary } from 'react-error-boundary';

const Home = React.lazy(() => import('./pages/Home'));
const Profile = React.lazy(() => import('./pages/Profile'));
const Settings = React.lazy(() => import('./pages/Settings'));

function App() {
  return (
    // ErrorBoundary перехватит ошибки загрузки чанков
    <ErrorBoundary fallback={<div>Ошибка загрузки страницы</div>}>
      <Suspense fallback={<div>Загрузка...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </ErrorBoundary>
  );
}

Пример: Suspense для данных через хук use() (React 18+)

import { use, Suspense } from 'react';

// Функция создаёт Promise с данными пользователя
async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json() as Promise<{ name: string; email: string }>;
}

// Компонент приостанавливает рендер, пока Promise не resolved
function UserCard({ userPromise }: { userPromise: Promise<{ name: string; email: string }> }) {
  const user = use(userPromise); // приостанавливает рендер
  return <div>{user.name} — {user.email}</div>;
}

function Page() {
  const userPromise = fetchUser('42');

  return (
    <Suspense fallback={<div>Загружаем данные пользователя...</div>}>
      <UserCard userPromise={userPromise} />
    </Suspense>
  );
}

Типичные ошибки

Путают lazy с обычным динамическим import() — не понимают, что lazy создаёт специальный React-объект

Забывают обернуть lazy-компонент в Suspense — приложение падает с ошибкой

Не добавляют Error Boundary рядом с Suspense, и сетевые ошибки загрузки никак не обрабатываются

Используют named export вместо default export с React.lazy

Думают, что Suspense в React 18 для данных работает «из коробки» без поддерживающей библиотеки или хука use()

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее