Разделение кода, ленивая загрузка и виртуализация

Урок про разделение кода (React.lazy + Suspense) и про идею виртуализации длинных списков — две техники, которые ускоряют не рендер, а загрузку и объём DOM.

Разделение кода (code splitting) — разбиение бандла на части, которые грузятся по требованию. Виртуализация — отрисовка только видимой части длинного списка.

Проблема: один огромный бандл

По умолчанию сборщик складывает весь JS в один файл. Пользователь, открывший главную, скачивает и код страницы настроек, и админку, и тяжёлый редактор — всё сразу. Это медленный первый запуск. Решение — грузить редко используемые куски лениво, когда они действительно нужны.

React.lazy и Suspense

React.lazy принимает функцию, которая динамически импортирует компонент. Сборщик выделит этот компонент в отдельный чанк. Пока чанк грузится, Suspense показывает запасной UI (fallback).

import React, { Suspense } from "react";

// чанк с Dashboard скачается только когда компонент понадобится
const Dashboard = React.lazy(() => import("./Dashboard"));

function App({ showDashboard }) {
  return (
    <Suspense fallback={<p>Загрузка…</p>}>
      {showDashboard && <Dashboard />}
    </Suspense>
  );
}

Типичные точки разделения: маршруты (каждая страница — свой чанк), тяжёлые модальные окна, редакторы, графики, всё, что не нужно на первом экране. Один Suspense может оборачивать несколько ленивых компонентов и показывать общий индикатор загрузки.

Когда code splitting оправдан

  • Бандл большой, и есть код, который нужен лишь части пользователей или лишь на определённых экранах.
  • Маршрутизация: ленивые маршруты — почти всегда хорошая идея в средних и крупных приложениях.
  • Не дробите слишком мелко: десятки крошечных чанков создают накладные расходы на сетевые запросы. Разумная гранулярность — страница/крупная фича.

Виртуализация длинных списков — идея

Представьте таблицу на 10 000 строк. Даже если каждый рендер дёшев, в DOM окажется 10 000 узлов — браузеру тяжело их разместить и прокручивать. Виртуализация (она же «оконный рендеринг», windowing) решает это так: отрисовывается только то, что попадает в видимую область прокрутки, плюс небольшой запас сверху и снизу. При скролле компоненты переиспользуются, подставляя данные следующих строк, а общая высота имитируется отступом-распоркой.

ПодходУзлов в DOM для 10 000 строк
наивный map~10 000 (тяжёлый DOM, лаги скролла)
виртуализация~20–40 (только видимые + запас)

На практике виртуализацию не пишут руками — берут библиотеку (react-window, react-virtuoso, @tanstack/react-virtual). Применяйте её, когда список реально длинный (сотни–тысячи строк) и тормозит. Для коротких списков это лишняя сложность.

Итог

  • React.lazy + Suspense грузят компоненты по требованию, ускоряя первый запуск.
  • Лучшие точки разделения — маршруты и тяжёлые редко используемые фичи.
  • Виртуализация рендерит только видимые строки списка — лекарство от тяжёлого DOM.
  • Обе техники — про «не делать лишнего заранее»; нужны под реальную нагрузку, а не везде.
Проверьте себя
1. Что делает React.lazy вместе с Suspense?
AКэширует данные с сервера
BГрузит компонент отдельным чанком по требованию, показывая fallback во время загрузки
CУскоряет все рендеры
DЗаменяет useState
2. Какая точка разделения кода почти всегда оправдана?
AКаждая мелкая кнопка
BМаршруты приложения
CКаждый useState
DАтрибуты className
3. В чём идея виртуализации длинного списка?
AСжимать данные в памяти
BРендерить только видимые строки плюс небольшой запас
CЗагружать список с CDN
DОтключать прокрутку
Поддержать проект