В 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, что позволяет
построить зависимость без лишних повторных запросов.
TanStack Router предоставляет объект parentData, который
автоматически содержит результат loader’ов всех родительских маршрутов.
Это ключевой механизм для построения зависимостей:
const profileLoader = async ({ parentData }) => {
const user = parentData.user;
const profileResponse = await fetch(`/api/profiles/${user.id}`);
return profileResponse.json();
};
Чтобы избежать лишних сетевых запросов и повысить скорость загрузки, 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 не начнёт выполнение до завершения родительского, что делает обработку зависимостей детерминированной.
В 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’ов.
Циклические зависимости: Если loader A зависит от loader B, а B от A, возникает бесконечная цепочка. TanStack Router не обрабатывает такие случаи автоматически, поэтому следует проектировать дерево маршрутов без циклов.
Повторные запросы при переходах: Дочерний loader
может вызываться заново при повторном рендере маршрута. Для
предотвращения лишних запросов рекомендуется кэширование
результатов или использование
useQuery/react-query внутри loader’а.
Ошибки загрузки родителя: Если родительский
loader завершился с ошибкой, дочерний loader не выполняется. Для
управления этим существует возможность обработки ошибок через
errorElement на уровне маршрута.
Для сложных зависимостей часто используют TanStack Query внутри loader’ов:
const userLoader = async ({ queryClient, params }) => {
return queryClient.fetchQuery(['user', params.userId], () =>
fetch(`/api/users/${params.userId}`).then(res => res.json())
);
};
Таким образом, зависимость между loader’ами становится не только структурной, но и оптимизированной по производительности.
Зависимости между loader’ами в TanStack Router создают мощный
инструмент для построения сложных приложений, где данные загружаются
предсказуемо и эффективно. Правильная организация родительско-дочерних
связей и использование parentData позволяют минимизировать
лишние запросы и упростить архитектуру.