Передача данных с сервера на клиент

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


Loader-функции и асинхронная загрузка данных

Каждый маршрут в TanStack Router может содержать loader-функцию — специальный метод, который вызывается перед рендером компонента маршрута. Loader позволяет:

  • Загружать данные с сервера;
  • Обрабатывать ошибки на уровне маршрута;
  • Кешировать результаты для повторного использования.

Пример определения маршрута с loader:

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

const rootRoute = createRouteConfig()
  .path('/')
  .component(RootComponent);

const userRoute = rootRoute.createRoute({
  path: 'user/:userId',
  component: UserComponent,
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.userId}`);
    if (!response.ok) {
      throw new Error('Ошибка при загрузке данных пользователя');
    }
    return response.json();
  }
});

В этом примере loader получает объект params, содержащий параметры маршрута, и возвращает данные пользователя. TanStack Router автоматически передает эти данные в компонент маршрута.


Использование данных внутри компонентов

Данные, возвращенные loader-функцией, доступны внутри компонента через хук useLoaderData. Этот хук обеспечивает строгую типизацию и синхронизацию с маршрутом.

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

function UserComponent() {
  const user = useLoaderData();

  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

Преимущество такого подхода — компонент получает готовые данные, и можно полностью отказаться от отдельного состояния загрузки или эффектов useEffect для запроса данных.


Параметры маршрута и их использование в loader

Loader-функция принимает объект с параметрами:

  • params — параметры маршрута (например, :userId);
  • context — произвольные данные, переданные через маршрутизатор;
  • query — объект с query-параметрами URL.

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

const searchRoute = rootRoute.createRoute({
  path: 'search',
  component: SearchComponent,
  loader: async ({ query }) => {
    const response = await fetch(`/api/search?q=${query.q}`);
    return response.json();
  }
});

В компоненте:

function SearchComponent() {
  const results = useLoaderData();
  return (
    <ul>
      {results.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

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

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

const userRoute = rootRoute.createRoute({
  path: 'user/:userId',
  component: UserComponent,
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.userId}`);
    if (!response.ok) {
      throw new Response('Пользователь не найден', { status: 404 });
    }
    return response.json();
  },
  errorComponent: UserErrorComponent,
});

Компонент UserErrorComponent получит объект ошибки через useRouteError() и сможет отображать соответствующее сообщение пользователю.

Для отображения индикаторов загрузки можно использовать useIsFetching или useTransition для отслеживания состояния маршрутов.


Кеширование и оптимизация запросов

TanStack Router позволяет кешировать результаты loader-функций, чтобы повторные переходы не выполняли лишние сетевые запросы:

  • loader автоматически кеширует данные по ключу маршрута и параметрам;
  • Для обновления данных можно использовать методы invalidate() или refetch() на маршруте.

Пример принудительного обновления данных:

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

function RefreshButton() {
  const router = useRouter();
  return (
    <button onCl ick={() => router.getRoute('user').refetch({ userId: '123' })}>
      Обновить данные пользователя
    </button>
  );
}

Серверный рендеринг и предварительная загрузка данных

TanStack Router полностью совместим с серверным рендерингом (SSR). Loader-функции можно вызывать на сервере для генерации HTML с уже загруженными данными. Это повышает производительность и улучшает SEO:

  1. На сервере вызываются loader для всех маршрутов текущего пути;
  2. Возвращенные данные сериализуются и вставляются в HTML;
  3. На клиенте маршрутизатор подхватывает эти данные и избегает повторных запросов.

Интеграция с TypeScript

TanStack Router поддерживает строгую типизацию данных loader и параметров маршрута. Например:

interface User {
  id: string;
  name: string;
  email: string;
}

const userRoute = rootRoute.createRoute<{ userId: string }, User>({
  path: 'user/:userId',
  component: UserComponent,
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.userId}`);
    return response.json() as Promise<User>;
  }
});

Типизация позволяет получать автодополнение и проверку типов в useLoaderData():

const user = useLoaderData<typeof userRoute>();

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


Практическое применение

Использование loader-функций в TanStack Router обеспечивает:

  • Консистентность данных на клиенте и сервере;
  • Чистую архитектуру компонентов без лишних эффектов;
  • Простое управление ошибками и состояниями загрузки;
  • Возможность предварительной загрузки данных и оптимизации переходов между маршрутами.

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