Loader-функции в TanStack Router представляют собой асинхронные функции, предназначенные для загрузки данных перед рендерингом компонента маршрута. Они обеспечивают возможность подготовки всех необходимых данных до того, как пользователь увидит страницу, что упрощает работу с асинхронными запросами и уменьшает количество состояний «загрузка/ошибка/успех» внутри компонентов.
1. Асинхронность: Loader-функции всегда возвращают промис. Router ожидает завершения промиса перед тем, как отобразить компонент. Если промис отклоняется, можно настроить отдельный маршрут для обработки ошибок.
2. Инкапсуляция логики загрузки данных: Все данные, необходимые для компонента маршрута, загружаются внутри loader. Компонент получает уже готовые данные через специальные хуки или пропсы, что делает компонент максимально чистым и сосредоточенным на визуализации.
3. Согласованность маршрутов: Каждый маршрут может иметь свой loader. Это позволяет строить сложные иерархии маршрутов, где родительский loader подготавливает общие данные, а дочерние маршруты загружают специфические.
Простейший пример loader-функции:
async function userLoader({ params }) {
const response = await fetch(`/api/users/${params.userId}`);
if (!response.ok) {
throw new Error('Не удалось загрузить данные пользователя');
}
return response.json();
}
Пояснения:
params — объект с параметрами маршрута, например,
userId.throw, что позволяет
обработать их через маршруты ошибок.useLoaderData.Loader-функции подключаются к маршруту при его определении:
import { createRouter, Route } from '@tanstack/router';
import UserPage from './UserPage';
const userRoute = new Route({
path: '/user/:userId',
component: UserPage,
loader: userLoader,
});
Здесь UserPage будет получать данные через
useLoaderData:
import { useLoaderData } from '@tanstack/router';
function UserPage() {
const user = useLoaderData();
return (
<div>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
Loader-функции позволяют строить иерархические зависимости. Родительский маршрут может возвращать данные, которые будут доступны всем дочерним маршрутам через контекст:
async function dashboardLoader() {
const stats = await fetch('/api/stats').then(res => res.json());
return { stats };
}
const dashboardRoute = new Route({
path: '/dashboard',
component: DashboardPage,
loader: dashboardLoader,
children: [
new Route({
path: 'reports',
component: ReportsPage,
loader: async ({ parentData }) => {
const reports = await fetch(`/api/reports?userId=${parentData.stats.userId}`).then(res => res.json());
return { reports };
},
}),
],
});
Особенности:
parentData содержит данные, возвращенные loader-ом
родителя.TanStack Router предоставляет возможность задавать маршруты ошибок:
const userRoute = new Route({
path: '/user/:userId',
component: UserPage,
loader: userLoader,
errorComponent: UserErrorPage,
});
Если userLoader выбросит ошибку, будет рендериться
UserErrorPage с объектом ошибки:
import { useRouteError } from '@tanstack/router';
function UserErrorPage() {
const error = useRouteError();
return <div>Ошибка загрузки пользователя: {error.message}</div>;
}
Loader-функции могут быть интегрированы с кешем для предотвращения повторных запросов:
const cache = new Map();
async function cachedLoader({ params }) {
if (cache.has(params.id)) return cache.get(params.id);
const data = await fetch(`/api/item/${params.id}`).then(res => res.json());
cache.set(params.id, data);
return data;
}
Это позволяет уменьшить нагрузку на сервер и ускоряет навигацию между маршрутами с одинаковыми данными.
throw для маршрутов
ошибок.Хотите, я подготовлю отдельный блок с лучшими практиками проектирования loader-функций для крупных приложений, чтобы видеть реальное применение TanStack Router в production?