TanStack Router предоставляет мощный механизм для управления маршрутами в приложениях на JavaScript, включая возможность последовательной загрузки данных. Под последовательной загрузкой подразумевается процесс, когда данные для каждого маршрута загружаются в строгой последовательности, предотвращая состояние гонки и обеспечивая согласованное отображение компонентов.
loader для маршрутовКаждый маршрут в TanStack Router может иметь функцию
loader, которая вызывается при переходе на этот маршрут.
Эта функция предназначена для асинхронной загрузки данных до рендеринга
компонента.
import { createRouter, Route } from '@tanstack/router';
const userRoute = new Route({
path: '/user/:userId',
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
});
Ключевой момент: loader выполняется до того, как
компонент будет смонтирован, что обеспечивает наличие всех необходимых
данных при рендеринге.
Для сложных маршрутов, где один маршрут зависит от данных другого,
TanStack Router поддерживает вложенные маршруты с их собственными
loader. Вложенные маршруты загружают данные в иерархическом
порядке:
loader родительского маршрута.loader дочернего
маршрута.Это позволяет гарантировать, что данные родителя будут доступны дочернему маршруту.
const postsRoute = new Route({
path: '/users/:userId/posts',
loader: async ({ parentData }) => {
const user = parentData.user;
const response = await fetch(`/api/users/${user.id}/posts`);
return response.json();
},
});
Здесь parentData предоставляет результаты загрузки
родительского маршрута (userRoute), что позволяет строить
цепочки данных без дополнительного состояния.
TanStack Router поддерживает обработку ошибок на уровне маршрута
через errorElement и блоки try/catch внутри
loader. При последовательной загрузке важно корректно
обрабатывать ошибки, чтобы последующие загрузки не выполнялись при сбое
предыдущих:
const userRoute = new Route({
path: '/user/:userId',
loader: async ({ params }) => {
try {
const response = await fetch(`/api/users/${params.userId}`);
if (!response.ok) throw new Error('User not found');
return response.json();
} catch (error) {
throw new Error('Ошибка загрузки данных пользователя');
}
},
errorElement: <UserErrorComponent />,
});
Это позволяет маршруту прерывать цепочку загрузки и отображать пользовательский компонент ошибки.
defer для ленивой загрузкиДля больших объёмов данных можно комбинировать последовательную
загрузку с ленивой, используя defer. defer
позволяет вернуть объект с промисами, которые будут разрешены после
рендеринга компонента:
const dashboardRoute = new Route({
path: '/dashboard',
loader: async () => {
return {
stats: fetch('/api/stats').then(res => res.json()),
notifications: fetch('/api/notifications').then(res => res.json()),
};
},
element: <Dashboard />,
});
Данные внутри defer могут обрабатываться асинхронно, но
последовательная логика родительских loader сохраняется.
Это позволяет оптимизировать время рендеринга и снизить блокировку
интерфейса.
useLoaderDataКомпоненты маршрутов получают данные через хук
useLoaderData, который предоставляет результат выполнения
loader. При последовательной загрузке важно использовать
данные родителя для инициализации дочерних компонентов:
import { useLoaderData } from '@tanstack/router';
function Posts() {
const { posts, user } = useLoaderData();
return (
<div>
<h2>Посты пользователя {user.name}</h2>
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
</div>
);
}
loader.parentData: обеспечивает
безопасный доступ к данным родителя, избегая повторных запросов.defer: ускоряет рендеринг
интерфейса, когда часть данных может загружаться параллельно.loader: следует избегать мутации состояния или
вызовов setState внутри loader, так как они
могут привести к расхождению данных и рендеров.Последовательная загрузка данных в TanStack Router обеспечивает строгую синхронизацию данных между маршрутами, упрощает управление асинхронными состояниями и повышает надежность интерфейса.