Контекст загрузки данных

TanStack Router предоставляет мощный механизм управления данными на уровне маршрутов через контекст загрузки данных (loader context). Этот механизм позволяет предварительно загружать данные для определённых маршрутов до рендеринга компонентов, обеспечивая предсказуемость состояния приложения и оптимизацию UX.


1. Основы контекста загрузки данных

Контекст загрузки данных создаётся с помощью loaders, которые являются функциями, ассоциированными с маршрутом. Эти функции вызываются автоматически при навигации на соответствующий маршрут. Основные особенности:

  • Асинхронность: Loaders могут возвращать промисы, позволяя выполнять сетевые запросы, запросы к IndexedDB или другим источникам данных.
  • Инкапсуляция: Каждый маршрут может иметь собственный loader, что упрощает управление зависимостями данных.
  • Наследование данных: Loaders родителей могут предоставлять данные дочерним маршрутам через контекст.

Пример базового loader’а:

import { createRouter, createRouteConfig } from '@tanstack/router';

const usersLoader = async () => {
  const response = await fetch('/api/users');
  if (!response.ok) throw new Error('Failed to fetch users');
  return response.json();
};

const routeConfig = createRouteConfig()
  .route({
    path: '/users',
    loader: usersLoader,
    component: UsersPage,
  });

const router = createRouter({ routeConfig });

2. Структура контекста данных

Контекст загрузки данных в TanStack Router представлен объектом с ключевыми элементами:

  • params — параметры маршрута (route.params), полученные из URL.
  • parentData — данные, предоставленные loader’ами родительских маршрутов.
  • signal — объект AbortSignal для отмены асинхронных операций при навигации.
  • context — объект для передачи дополнительных данных через router, доступный всем loader’ам и компонентам.

Пример использования контекста:

const userDetailsLoader = async ({ params, parentData, signal }) => {
  const userId = params.userId;
  const user = parentData?.users?.find(u => u.id === userId);
  
  if (!user) {
    const response = await fetch(`/api/users/${userId}`, { signal });
    if (!response.ok) throw new Error('User not found');
    return response.json();
  }

  return user;
};

3. Обработка ошибок и состояние загрузки

TanStack Router позволяет централизованно обрабатывать ошибки, возникающие при загрузке данных, а также управлять состоянием загрузки:

  • errorElement — компонент, отображаемый при ошибке загрузки.
  • loadingElement — компонент для отображения состояния загрузки данных.
  • catch — механизм для отлова и перенаправления на страницу ошибки.

Пример:

const routeConfig = createRouteConfig()
  .route({
    path: '/users/:userId',
    loader: userDetailsLoader,
    component: UserDetailsPage,
    errorElement: UserErrorPage,
    loadingElement: LoadingSpinner,
  });

При этом loader автоматически получает signal для отмены запроса, если пользователь покидает маршрут до завершения загрузки.


4. Кэширование и оптимизация

Для снижения количества повторных запросов TanStack Router поддерживает кэширование данных loader’ов. Данные сохраняются на уровне маршрута и переиспользуются при повторном рендере:

  • keepPreviousData — позволяет сохранять старые данные во время загрузки новых.
  • invalidate — ручное обновление данных loader’а.

Пример использования:

const usersLoader = async ({ context }) => {
  if (context.cachedUsers) return context.cachedUsers;

  const response = await fetch('/api/users');
  const users = await response.json();
  context.cachedUsers = users;
  return users;
};

5. Взаимодействие с компонентами

Loader’ы TanStack Router тесно интегрированы с React-компонентами. Данные, возвращённые loader’ом, доступны в компоненте через useLoaderData():

import { useLoaderData } from '@tanstack/router';

function UsersPage() {
  const users = useLoaderData();
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Преимущества такого подхода:

  • Компоненты получают полностью загруженные данные.
  • Минимизируются побочные эффекты внутри компонентов.
  • Обеспечивается согласованность данных между навигациями.

6. Сложные сценарии: вложенные маршруты

В случае вложенных маршрутов loader’ы могут работать каскадно. Родительский loader передает данные дочерним, а дочерние могут их расширять:

const rootLoader = async () => {
  const response = await fetch('/api/config');
  return response.json();
};

const dashboardLoader = async ({ parentData }) => {
  const settings = parentData.config;
  const response = await fetch(`/api/dashboard?theme=${settings.theme}`);
  return response.json();
};

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


7. Сигнал отмены и асинхронная безопасность

Каждый loader получает AbortSignal, что позволяет безопасно отменять запросы при навигации:

const safeLoader = async ({ signal }) => {
  const response = await fetch('/api/data', { signal });
  return response.json();
};

Это предотвращает утечки памяти и отображение устаревших данных, особенно при быстрых переходах между маршрутами.


8. Передача глобального контекста

TanStack Router поддерживает передачу глобального состояния через context:

const router = createRouter({
  routeConfig,
  context: { apiToken: '123456' },
});

const loaderWithContext = async ({ context }) => {
  const response = await fetch('/api/secure-data', {
    headers: { Authorization: `Bearer ${context.apiToken}` },
  });
  return response.json();
};

Это удобно для передачи токенов, настроек пользователя или других глобальных данных, доступных всем loader’ам.


Контекст загрузки данных в TanStack Router обеспечивает мощный и гибкий способ управления асинхронными данными на уровне маршрутов. Он сочетает в себе обработку ошибок, управление состоянием загрузки, кэширование и поддержку сложной иерархии маршрутов, что делает его ключевым инструментом для построения масштабируемых React-приложений.