Dev Highlights

Доки по разработке

This project is maintained by teniryte

5. Параллельные и зависимые запросы

Реальные экраны редко состоят из одного запроса. Обычно нужно:

Когда достаточно нескольких useQuery

Если запросов мало и они независимы, можно просто написать их рядом:

const userQuery = useQuery(userOptions(userId))
const statsQuery = useQuery(statsOptions(userId))
const notificationsQuery = useQuery(notificationsOptions(userId))

Такой код уже будет работать параллельно.

useQueries

Когда запросов много или список динамический, используйте useQueries.

import { useQueries } from '@tanstack/react-query'

function Dashboard({ userId }: { userId: string }) {
  const results = useQueries({
    queries: [
      userOptions(userId),
      statsOptions(userId),
      notificationsOptions(userId),
    ],
  })

  const [user, stats, notifications] = results

  if (results.some((query) => query.isPending)) return <DashboardSkeleton />
  if (results.some((query) => query.isError)) return <ErrorState />

  return (
    <DashboardView
      user={user.data}
      stats={stats.data}
      notifications={notifications.data}
    />
  )
}

combine

combine позволяет агрегировать результат в одну структуру:

const dashboard = useQueries({
  queries: [userOptions(userId), statsOptions(userId)],
  combine: (results) => ({
    isPending: results.some((query) => query.isPending),
    isError: results.some((query) => query.isError),
    data: {
      user: results[0].data,
      stats: results[1].data,
    },
  }),
})

Это уменьшает шум в компоненте и особенно полезно для container-компонентов.

Зависимые запросы через enabled

Классический случай:

const userQuery = useQuery(userOptions(userId))

const projectsQuery = useQuery({
  ...projectsOptions(userQuery.data?.teamId ?? ''),
  enabled: !!userQuery.data?.teamId,
})

Так второй запрос:

skipToken для зависимых запросов

const projectQuery = useQuery({
  queryKey: ['projects', teamId],
  queryFn: teamId ? () => fetchProjects(teamId) : skipToken,
})

Подходит, если нужен более строгий TypeScript-контроль.

Самая частая ошибка: waterfall

Плохой сценарий:

  1. грузим пользователя
  2. после рендера стартуем проекты
  3. после их завершения стартуем задачи

Так экран становится заметно медленнее, чем мог бы быть.

Что делать:

Удаление request waterfall через prefetch

await Promise.all([
  queryClient.ensureQueryData(userOptions(userId)),
  queryClient.ensureQueryData(statsOptions(userId)),
  queryClient.ensureQueryData(notificationsOptions(userId)),
])

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

useSuspenseQueries

Если экран работает через Suspense:

import { useSuspenseQueries } from '@tanstack/react-query'

const [user, stats] = useSuspenseQueries({
  queries: [userOptions(userId), statsOptions(userId)],
})

Ограничения актуального API:

Практический вывод: useSuspenseQueries хорош для набора запросов, которые точно должны стартовать сразу.

Динамические списки запросов

const orders = useQueries({
  queries: orderIds.map((id) => orderOptions(id)),
})

Подводные камни:

Когда объединить запросы на бэкенде

Если на экране 10-20 мелких запросов, это не всегда повод писать 20 useQuery. Иногда лучше сделать один агрегированный endpoint.

Признаки, что стоит объединить:

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