Освой механизм приостановки, ленивую загрузку компонентов и разделение бандла.
Открыть этот урок в KodokonSuspense опирается на малоизвестный механизм: компонент, у которого пока нет ни данных, ни кода, приостанавливает свой рендеринг. Под капотом текущая реализация выбрасывает промис, который React перехватывает так же, как перехватил бы ошибку. Затем React поднимается до ближайшей границы <Suspense>, показывает её fallback, а потом проигрывает рендер поддерева заново, как только промис разрешится. Это деталь реализации (никогда не выбрасывай промис сам), но она объясняет всё наблюдаемое поведение: граница работает как декларативный catch для асинхронной работы. Исторический сценарий использования - разделение кода через React.lazy.
import { lazy, Suspense } from 'react';
const Chart = lazy(() => import('./Chart'));
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<Chart points={data} />
</Suspense>
);
}lazy ожидает функцию, которая возвращает промис, разрешающийся в модуль с экспортом default. Само разделение - работа сборщика: каждый динамический import() становится точкой разделения и порождает отдельный чанк, загружаемый по требованию. Частая тонкость в реальных кодовых базах: твои компоненты нередко являются именованными экспортами. Тогда модуль приходится адаптировать на лету.
const Chart = lazy(() =>
import('./charts').then((module) => ({
default: module.Chart,
}))
);Сложность в продакшене: во время навигации уже показанный контент может снова приостановиться (новый маршрут, новый чанк) и резко смениться на fallback. Начиная с React 18 обновление, обёрнутое в startTransition, меняет это поведение: React оставляет предыдущий интерфейс на экране, пока новый рендер приостановлен в фоне, и показывает fallback только для контента, который ещё ни разу не был раскрыт. Границы к тому же композируются в глубину: несколько вложенных границ <Suspense> позволяют раскрывать интерфейс зона за зоной, от общего скелетона до деталей.
const [isPending, startTransition] = useTransition();
function openTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// The current tab stays visible while the new
// one loads, instead of dropping back to the fallback.React.lazy?