Зависимости между loader'ами

В TanStack Router loader отвечает за асинхронную загрузку данных, которые требуются для отображения определённого маршрута. Часто возникает ситуация, когда один loader зависит от результата другого. Для управления такими зависимостями существует несколько подходов, позволяющих строить сложные цепочки загрузки данных, минимизировать повторные запросы и сохранять предсказуемое поведение приложения.


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

Каждый loader в TanStack Router получает объект context и параметры маршрута. Параметры маршрута могут использоваться как источник для передачи промежуточных данных между loader’ами:

const userLoader = async ({ params }) => {
  const response = await fetch(`/api/users/${params.userId}`);
  return response.json();
};

const postsLoader = async ({ parentData }) => {
  const user = parentData.user;
  const response = await fetch(`/api/posts?userId=${user.id}`);
  return response.json();
};

const routes = [
  {
    path: "/users/:userId",
    loader: userLoader,
    children: [
      {
        path: "posts",
        loader: postsLoader
      }
    ]
  }
];

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


parentData: доступ к данным родителя

TanStack Router предоставляет объект parentData, который автоматически содержит результат loader’ов всех родительских маршрутов. Это ключевой механизм для построения зависимостей:

  • Эффективная передача данных: нет необходимости повторно запрашивать данные, уже загруженные на более высоком уровне маршрута.
  • Упрощение логики: дочерние loader’ы могут работать только с необходимыми им данными, не заботясь о том, как они были загружены.
const profileLoader = async ({ parentData }) => {
  const user = parentData.user;
  const profileResponse = await fetch(`/api/profiles/${user.id}`);
  return profileResponse.json();
};

Оптимизация цепочек loader’ов

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

const loaderA = async () => {
  return fetch("/api/dataA").then(res => res.json());
};

const loaderB = async ({ parentData }) => {
  const dataA = parentData.dataA;
  return fetch(`/api/dataB?ref=${dataA.ref}`).then(res => res.json());
};

const routes = [
  {
    path: "/a",
    loader: loaderA,
    children: [
      {
        path: "b",
        loader: loaderB
      }
    ]
  }
];

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


Совместное использование нескольких loader’ов на одном уровне

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

const loaderX = async () => fetch("/api/x").then(r => r.json());
const loaderY = async () => fetch("/api/y").then(r => r.json());

const combinedLoader = async ({ parentData }) => {
  const [x, y] = await Promise.all([loaderX(), loaderY()]);
  return { x, y };
};

Если между loader’ами есть зависимость, тогда нужно использовать parentData или выстраивать вложенные маршруты. Параллельное выполнение допустимо только для независимых loader’ов.


Проблемы и решения при сложных зависимостях

  1. Циклические зависимости: Если loader A зависит от loader B, а B от A, возникает бесконечная цепочка. TanStack Router не обрабатывает такие случаи автоматически, поэтому следует проектировать дерево маршрутов без циклов.

  2. Повторные запросы при переходах: Дочерний loader может вызываться заново при повторном рендере маршрута. Для предотвращения лишних запросов рекомендуется кэширование результатов или использование useQuery/react-query внутри loader’а.

  3. Ошибки загрузки родителя: Если родительский loader завершился с ошибкой, дочерний loader не выполняется. Для управления этим существует возможность обработки ошибок через errorElement на уровне маршрута.


Интеграция с TanStack Query

Для сложных зависимостей часто используют TanStack Query внутри loader’ов:

  • Loader возвращает объект Query, который автоматически кэширует данные.
  • Дочерние loader’ы могут использовать уже закэшированные результаты.
const userLoader = async ({ queryClient, params }) => {
  return queryClient.fetchQuery(['user', params.userId], () =>
    fetch(`/api/users/${params.userId}`).then(res => res.json())
  );
};

Таким образом, зависимость между loader’ами становится не только структурной, но и оптимизированной по производительности.


Рекомендации по организации зависимостей

  • Дочерние loader’ы должны использовать parentData, если данные загружены на уровне родителя.
  • Избегать циклов и ненужного дублирования запросов.
  • Использовать асинхронное выполнение Promise.all для независимых loader’ов, чтобы ускорить загрузку.
  • Встроить кэширование и обработку ошибок, чтобы загрузка зависимых данных была предсказуемой и стабильной.

Зависимости между loader’ами в TanStack Router создают мощный инструмент для построения сложных приложений, где данные загружаются предсказуемо и эффективно. Правильная организация родительско-дочерних связей и использование parentData позволяют минимизировать лишние запросы и упростить архитектуру.