В реальных приложениях данные редко существуют изолированно. Один запрос часто зависит от результата другого: идентификатор пользователя нужен для загрузки его профиля, данные профиля требуются для получения списка заказов, а список заказов может определять дальнейшие вычисления и фильтрации. Подобные цепочки формируют основу зависимых запросов, где выполнение одного запроса невозможно без успешного завершения предыдущего.
TanStack Query предоставляет несколько механизмов для построения таких связей, сохраняя при этом декларативность, кэширование и контроль над состояниями загрузки.
Зависимый запрос — это запрос, который не выполняется до тех пор, пока не будут доступны данные из другого запроса.
Ключевая особенность заключается в управлении
enabled:
useQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
enabled: !!userId
})
В этом примере запрос выполняется только тогда, когда
userId существует. Это самый простой вариант зависимости,
основанный на наличии входного параметра.
Типичный сценарий: сначала загружается пользователь, затем его профиль расширенного типа.
const { data: user } = useQuery({
queryKey: ['user'],
queryFn: fetchUser
})
const userId = user?.id
const { data: profile } = useQuery({
queryKey: ['profile', userId],
queryFn: () => fetchProfile(userId),
enabled: !!userId
})
Здесь второй запрос зависит от результата первого. Основные особенности:
profile не запрашивается до появления
user.iduserIdКлюч запроса определяет кэш, дедупликацию и повторное использование данных. Ошибки в структуре ключа приводят к:
Правильный подход:
queryKey: ['profile', userId]
Неправильный подход:
queryKey: ['profile']
Во втором случае все пользователи будут делить один кэш, что делает зависимость логически некорректной.
Сценарий цепочки:
const { data: user } = useQuery({
queryKey: ['user'],
queryFn: fetchUser
})
const userId = user?.id
const { data: organization } = useQuery({
queryKey: ['organization', userId],
queryFn: () => fetchOrganization(userId),
enabled: !!userId
})
const orgId = organization?.id
const { data: settings } = useQuery({
queryKey: ['settings', orgId],
queryFn: () => fetchSettings(orgId),
enabled: !!orgId
})
Каждый следующий уровень активируется только при наличии данных предыдущего уровня. Такая структура формирует направленный граф зависимостей.
enabled может быть не только проверкой наличия значения,
но и сложным логическим условием.
enabled: !!userId && user.status === 'active'
Или комбинацией нескольких факторов:
enabled: !!userId && !!organizationId && hasAccess
Важно учитывать, что enabled пересчитывается при каждом
рендере, поэтому он должен оставаться чистым выражением без побочных
эффектов.
Иногда один запрос зависит от нескольких независимых источников данных.
const { data: user } = useQuery({
queryKey: ['user'],
queryFn: fetchUser
})
const { data: permissions } = useQuery({
queryKey: ['permissions'],
queryFn: fetchPermissions
})
const userId = user?.id
const canLoadDashboard = permissions?.includes('dashboard')
const { data: dashboard } = useQuery({
queryKey: ['dashboard', userId],
queryFn: () => fetchDashboard(userId),
enabled: !!userId && canLoadDashboard
})
Здесь выполнение зависит сразу от двух независимых источников.
Иногда зависимость можно упростить, используя select,
чтобы уменьшить количество промежуточных вычислений.
const { data: userId } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
select: (data) => data.id
})
const { data: profile } = useQuery({
queryKey: ['profile', userId],
queryFn: () => fetchProfile(userId),
enabled: !!userId
})
Преимущество подхода:
TanStack Query не требует строгой последовательности выполнения. Если данные уже закэшированы, зависимый запрос может выполниться мгновенно.
enabled: !!userId
При этом:
userId уже известен из кэша, запрос не
задерживаетсяЗависимые запросы могут привести к каскадным перезапросам при каждом изменении промежуточного значения.
Типичная ошибка:
enabled: !!user?.id
Если user пересоздаётся на каждом рендере, возможны
лишние активации.
Более стабильный вариант:
const userId = useMemo(() => user?.id, [user?.id])
enabled: !!userId
Хотя зависимые запросы чаще связываются с useQuery, на
практике они часто сочетаются с мутациями.
const mutation = useMutation({
mutationFn: createOrder,
onSuccess: (order) => {
queryClient.invalidateQueries({
queryKey: ['orders', order.userId]
})
}
})
После мутации запускается обновление зависимого списка, что формирует обратную зависимость: результат мутации влияет на последующие запросы.
Иногда структура зависимостей становится ветвящейся:
if (user?.role === 'admin') {
// загружаются админ-данные
}
В TanStack Query это выражается через условные
enabled:
const isAdmin = user?.role === 'admin'
const { data: adminData } = useQuery({
queryKey: ['admin-data', user?.id],
queryFn: fetchAdminData,
enabled: !!user?.id && isAdmin
})
Чем глубже цепочка зависимостей, тем выше вероятность:
Типичная проблема:
user → organization → projects → tasks → comments
Каждый шаг блокирует следующий, создавая последовательную загрузку вместо параллельной.
Одним из решений является частичное распараллеливание:
const { data: user } = useQuery({
queryKey: ['user'],
queryFn: fetchUser
})
const userId = user?.id
const { data: organization } = useQuery({
queryKey: ['organization', userId],
queryFn: () => fetchOrganization(userId),
enabled: !!userId
})
const { data: projects } = useQuery({
queryKey: ['projects', userId],
queryFn: () => fetchProjects(userId),
enabled: !!userId
})
Здесь проекты и организация загружаются параллельно, хотя оба зависят от userId.
Для множественных зависимых запросов применяется
useQueries:
const results = useQueries({
queries: [
{
queryKey: ['profile', userId],
queryFn: () => fetchProfile(userId),
enabled: !!userId
},
{
queryKey: ['settings', userId],
queryFn: () => fetchSettings(userId),
enabled: !!userId
}
]
})
Такой подход упрощает управление группой зависимых запросов, сохраняя их независимое выполнение.
Кэш играет ключевую роль в производительности цепочек зависимостей:
Корректная настройка кэша уменьшает влияние глубокой зависимости на UX.
На практике чаще всего встречаются следующие проблемы:
queryKeyenabled без проверки всех
зависимостейqueryFnКаждая из этих ошибок приводит либо к лишним запросам, либо к неконсистентным данным.
Внутри TanStack Query зависимость реализуется не как явный граф, а как реактивная система условий:
enabledТакая модель обеспечивает предсказуемое поведение без необходимости ручного управления порядком выполнения.