В TanStack Query любой запрос проходит последовательность состояний и этапов обработки. Библиотека отслеживает:
Жизненный цикл запроса строится вокруг объекта Query, который
хранится внутри Query Cache. Компоненты через useQuery()
подписываются на этот объект и автоматически получают обновления
состояния.
Полный цикл выглядит следующим образом:
useQueryqueryFngcTime запрос удаляется из памятиКаждый запрос начинается с вызова useQuery.
import { useQuery } from '@tanstack/react-query'
function Users() {
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
return <div>...</div>
}
В этот момент TanStack Query:
Ключ queryKey играет центральную роль. Именно он
идентифицирует запрос.
Перед выполнением сетевого запроса библиотека всегда проверяет Query Cache.
Если данные уже существуют:
queryKey: ['users']
то возможны два сценария:
Если данные еще не устарели (staleTime не истек), запрос
к серверу не выполняется.
Компонент сразу получает данные из памяти.
Если данные stale, TanStack Query:
Это один из важнейших механизмов библиотеки.
Во время первого выполнения запроса состояние становится pending.
const {
status
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Возможные значения:
pending
success
error
При pending:
status === 'pending'
или:
isPending === true
Компонент обычно показывает loader.
if (query.isPending) {
return <Spinner />
}
queryFn — функция получения данных.
async function fetchUsers() {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Ошибка загрузки')
}
return response.json()
}
TanStack Query:
Если Promise успешно завершился — запрос получает статус success.
Если Promise выбросил исключение — статус становится error.
После успешного получения данных:
{
status: 'success'
}
в Query Cache сохраняются:
Компоненты автоматически перерисовываются.
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
После завершения запроса данные остаются в памяти.
Это позволяет:
Пример:
function UsersList() {
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
}
function SidebarUsers() {
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
}
HTTP-запрос выполнится только один раз.
Оба компонента используют общий Query Cache.
Одно из важнейших понятий TanStack Query — свежесть данных.
По умолчанию данные считаются stale сразу после получения.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Эквивалентно:
staleTime: 0
staleTime определяет время свежести данных.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 1000 * 60
})
В течение минуты:
Когда данные становятся stale:
Это важное отличие от классического подхода с loader на каждое обновление.
Фоновое обновление — ключевая особенность TanStack Query.
При stale данных библиотека может автоматически обновлять запрос:
Во время background refetch:
isFetching === true
Но:
isPending === false
То есть данные уже есть, но происходит обновление.
Означает:
Означает:
Пример:
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
query.isPending
query.isFetching
При ошибке:
status === 'error'
в объекте запроса появляется:
error
Пример:
if (query.isError) {
return <div>{query.error.message}</div>
}
Ошибка сохраняется внутри Query Cache.
По умолчанию TanStack Query автоматически повторяет запросы.
retry: 3
Поведение:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: 5
})
Полное отключение:
retry: false
Задержка между попытками:
retryDelay: 1000
Либо функция:
retryDelay: attempt =>
Math.min(1000 * 2 ** attempt, 30000)
Так реализуется exponential backoff.
Когда компонент размонтируется:
return null
TanStack Query не удаляет Query сразу.
Запрос становится inactive.
Inactive Query:
Если компонент снова смонтируется:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
данные будут взяты из кэша.
Через некоторое время inactive Query удаляется.
Настройка:
gcTime: 1000 * 60 * 5
По умолчанию — 5 минут.
После истечения времени:
Эти параметры часто путают.
Отвечает за свежесть данных.
Отвечает за время хранения inactive Query в памяти.
useQuery({
queryKey: ['profile'],
queryFn: fetchProfile,
staleTime: 60000,
gcTime: 300000
})
Сценарий:
По умолчанию TanStack Query обновляет stale запросы при возврате фокуса окна.
Например:
Настройка:
refetchOnWindowFocus: false
При восстановлении интернета:
refetchOnReconnect: true
TanStack Query автоматически обновляет stale запросы.
При повторном mount:
refetchOnMount: true
Если данные stale — запрос обновится.
Периодическое обновление:
refetchInterval: 5000
Запрос будет выполняться каждые 5 секунд.
Даже без действий пользователя.
TanStack Query поддерживает AbortController.
useQuery({
queryKey: ['users'],
queryFn: async ({ signal }) => {
const response = await fetch('/api/users', {
signal
})
return response.json()
}
})
При отмене:
Если несколько компонентов одновременно вызывают одинаковый запрос:
['users']
TanStack Query выполняет только один HTTP-запрос.
Остальные подписываются на тот же Promise.
Каждый useQuery() создает Query Observer.
Observer:
Один Query может иметь множество Observer.
Внутри жизненного цикла Query может переходить между состояниями:
pending -> success
pending -> error
success -> fetching
error -> fetching
При refetch:
success + fetching
То есть старые данные продолжают существовать.
Внутреннее состояние Query содержит:
{
data,
error,
status,
fetchStatus,
dataUpdatedAt,
errorUpdatedAt
}
fetchStatus отличается от status.
Возможные значения:
fetching
paused
idle
Пример:
status: 'success'
fetchStatus: 'fetching'
Это означает:
Если сеть недоступна:
fetchStatus === 'paused'
После восстановления соединения запрос продолжится.
Любой Query можно обновить вручную.
const { refetch } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Вызов:
await refetch()
Invalidate — ключевой механизм обновления данных.
queryClient.invalidateQueries({
queryKey: ['users']
})
После invalidation:
После mutation часто выполняется invalidation.
await mutation.mutateAsync(data)
queryClient.invalidateQueries({
queryKey: ['users']
})
Это запускает новый цикл:
stale -> fetching -> success
Временные данные:
placeholderData: []
Используются до завершения запроса.
Но не сохраняются в кэш.
Начальные данные:
initialData: []
В отличие от placeholderData:
Полезно для пагинации.
useQuery({
queryKey: ['users', page],
queryFn: () => fetchUsers(page),
placeholderData: keepPreviousData
})
Старые данные остаются на экране во время загрузки новой страницы.
Изменение ключа создает новый Query.
['users', 1]
['users', 2]
Это два независимых жизненных цикла.
Запрос можно заранее поместить в кэш.
await queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
При последующем mount:
При SSR жизненный цикл меняется.
Сервер:
Клиент:
TanStack Query Devtools позволяют видеть:
Подключение:
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
<ReactQueryDevtools initialIsOpen={false} />
mount компонента
↓
поиск Query в кэше
↓
fresh? ── yes ──→ возврат кэша
↓ no
запуск queryFn
↓
pending
↓
success/error
↓
сохранение в кэш
↓
данные становятся stale
↓
background refetch
↓
inactive
↓
garbage collection