Loader-функции и их назначение

Loader-функции в TanStack Router представляют собой асинхронные функции, предназначенные для загрузки данных перед рендерингом компонента маршрута. Они обеспечивают возможность подготовки всех необходимых данных до того, как пользователь увидит страницу, что упрощает работу с асинхронными запросами и уменьшает количество состояний «загрузка/ошибка/успех» внутри компонентов.


Основные принципы работы Loader-функций

1. Асинхронность: Loader-функции всегда возвращают промис. Router ожидает завершения промиса перед тем, как отобразить компонент. Если промис отклоняется, можно настроить отдельный маршрут для обработки ошибок.

2. Инкапсуляция логики загрузки данных: Все данные, необходимые для компонента маршрута, загружаются внутри loader. Компонент получает уже готовые данные через специальные хуки или пропсы, что делает компонент максимально чистым и сосредоточенным на визуализации.

3. Согласованность маршрутов: Каждый маршрут может иметь свой loader. Это позволяет строить сложные иерархии маршрутов, где родительский 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 в маршруте

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-ом родителя.
  • Дочерние маршруты могут использовать уже загруженные данные без повторного запроса.

Обработка ошибок в 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-функций

  • Синхронность рендера и данных: компонент получает уже загруженные данные.
  • Централизованная логика загрузки: уменьшает дублирование кода по fetch-запросам.
  • Простота обработки ошибок: маршруты ошибок позволяют выделить UI для неудачных запросов.
  • Поддержка вложенных маршрутов: loader-ы легко комбинируются и используют данные родителей.

Взаимодействие loader с кешированием и оптимизацией

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;
}

Это позволяет уменьшить нагрузку на сервер и ускоряет навигацию между маршрутами с одинаковыми данными.


Ключевые моменты работы с loader

  • Всегда возвращают промис с данными.
  • Могут использовать параметры маршрута и контекст родителя.
  • Ошибки выбрасываются через throw для маршрутов ошибок.
  • Идеально подходят для загрузки данных до рендера компонента.
  • Позволяют строить согласованную и предсказуемую архитектуру маршрутов с вложенностью.

Хотите, я подготовлю отдельный блок с лучшими практиками проектирования loader-функций для крупных приложений, чтобы видеть реальное применение TanStack Router в production?