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

Урок про разделение кода (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Грузит компонент отдельным чанком по требованию, показывая fallback во время загрузки
BКэширует данные с сервера
CУскоряет все рендеры
DЗаменяет useState
2. Какая точка разделения кода почти всегда оправдана?
AКаждая мелкая кнопка
BМаршруты приложения
CКаждый useState
DАтрибуты className
3. В чём идея виртуализации длинного списка?
AСжимать данные в памяти
BЗагружать список с CDN
CРендерить только видимые строки плюс небольшой запас
DОтключать прокрутку