React Router предоставляет мощный механизм для маршрутизации, но при
работе с большими приложениями часто возникает необходимость
отложенной загрузки данных, чтобы улучшить
пользовательский опыт и ускорить рендеринг. В React Router 6.4+ появился
инструмент defer, который позволяет
загружать данные асинхронно и отображать страницу до завершения всех
запросов.
deferФункция defer используется внутри loader-функций
маршрутов для возврата объекта, содержащего как синхронные, так
и асинхронные данные. Структура возвращаемого объекта может выглядеть
так:
import { defer } from "react-router-dom";
export function loader() {
return defer({
user: fetchUser(), // Promise
posts: fetchPosts(), // Promise
timestamp: Date.now() // Синхронное значение
});
}
defer полезен для разделения данных, которые не
мешают первому рендеру, от тех, которые можно загрузить
позже.
Await для обработки отложенных данныхДля работы с объектом, возвращённым defer, используется
компонент <Await>. Он позволяет
отобразить загрузку, пока данные ещё не пришли, и
рендерить компонент после их получения:
import { useLoaderData, Await } from "react-router-dom";
import { Suspense } from "react";
export default function Dashboard() {
const data = useLoaderData();
return (
<div>
<h1>Панель управления</h1>
<Suspense fallback={<p>Загрузка пользователя...</p>}>
<Await resolve={data.user}>
{(user) => <p>Привет, {user.name}!</p>}
</Await>
</Suspense>
<Suspense fallback={<p>Загрузка постов...</p>}>
<Await resolve={data.posts}>
{(posts) => (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)}
</Await>
</Suspense>
<p>Время запроса: {data.timestamp}</p>
</div>
);
}
Ключевые моменты:
useLoaderData() возвращает объект с данными, включая
промисы.Suspense указывает React, что внутри блока может быть
отложенный рендер.<Await> ждёт разрешения промиса и передаёт
результат в рендер-функцию.Awaitdefer позволяет одновременно работать с несколькими
асинхронными запросами. Их можно обрабатывать
параллельно, не блокируя общий рендер:
<Await resolve={Promise.all([data.user, data.posts])}>
{([user, posts]) => (
<>
<p>{user.name}</p>
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</>
)}
</Await>
Это полезно, когда необходимо дождаться нескольких зависимостей до выполнения определенной логики, но при этом использовать возможности Suspense.
Асинхронные данные через defer могут завершаться с
ошибкой. React Router предоставляет ErrorBoundary и
проп errorElement для обработки ошибок:
export const route = {
path: "/dashboard",
loader: loader,
element: <Dashboard />,
errorElement: <DashboardError />
};
Ошибки промисов внутри Await можно обработать через
catch или через собственные границы ошибок.
deferdefer для данных, критичных для
первоначального рендера, иначе пользователю придётся ждать пустой
экран.Suspense и <Await> для
создания локализованных индикаторов загрузки.Promise.all внутри <Await>
только тогда, когда нужно дождаться нескольких зависимостей
одновременно.defer превращает маршруты в асинхронные, не
блокирующие интерфейс, и позволяет строить приложения с
динамической подгрузкой данных без потери отзывчивости.