TanStack Query предоставляет набор хуков, объектов конфигурации и API-клиентов для управления серверным состоянием. Основой синтаксиса библиотеки являются:
useQueryuseMutationqueryKeyqueryFnQueryClientinvalidateQueries, setQueryData,
prefetchQueryБиблиотека строится вокруг декларативного описания запросов. Вместо ручного управления состоянием загрузки, ошибками и кешированием разработчик описывает:
Базовый синтаксис:
const query = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Здесь используются два обязательных параметра:
| Параметр | Назначение |
|---|---|
queryKey |
Уникальный ключ кеша |
queryFn |
Функция загрузки данных |
Возвращаемое значение содержит объект состояния запроса:
const {
data,
error,
isLoading,
isError,
isSuccess,
refetch
} = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
queryKey — идентификатор кеша запроса.
TanStack Query использует ключ для:
queryKey: ['users']
Ключ всегда рекомендуется задавать массивом.
Даже если используется одно значение, массив обеспечивает:
Очень часто ключ зависит от параметров:
queryKey: ['user', userId]
Пример:
const { data } = useQuery({
queryKey: ['user', 15],
queryFn: () => fetchUser(15)
})
TanStack Query создаст отдельный кеш для каждого
userId.
Допускается использование объектов:
queryKey: [
'posts',
{
page: 1,
limit: 20,
sort: 'date'
}
]
Это особенно полезно при:
Ключ должен быть:
Нежелательный вариант:
queryKey: ['users', new Date()]
Такой ключ будет постоянно изменяться.
Корректный вариант:
queryKey: ['users', dateString]
queryFn — функция, которая выполняет запрос.
Пример:
const fetchUsers = async () => {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Ошибка загрузки')
}
return response.json()
}
Использование:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Функция должна возвращать Promise.
Допустимы:
queryFn: async () => {
return await fetchData()
}
или:
queryFn: () => fetchData()
TanStack Query передаёт объект контекста:
useQuery({
queryKey: ['user', userId],
queryFn: ({ queryKey }) => {
const [, id] = queryKey
return fetchUser(id)
}
})
Это позволяет строить универсальные функции.
Объект содержит:
| Поле | Назначение |
|---|---|
queryKey |
Ключ запроса |
signal |
AbortSignal |
meta |
Дополнительные данные |
TanStack Query поддерживает отмену запросов.
Пример:
useQuery({
queryKey: ['users'],
queryFn: async ({ signal }) => {
const response = await fetch('/api/users', {
signal
})
return response.json()
}
})
Если компонент размонтируется или запрос станет неактуальным, библиотека отменит fetch.
enabled управляет автоматическим запуском запроса.
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: false
})
Запрос не выполнится автоматически.
Одна из самых частых схем:
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId
})
Запрос выполнится только после появления userId.
staleTime определяет время актуальности данных.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 60000
})
В течение 60 секунд данные считаются свежими.
Пока данные свежие:
После истечения времени данные становятся stale.
staleTime: 0
Поведение по умолчанию.
staleTime: 1000 * 60 * 5
Пять минут свежих данных.
staleTime: Infinity
Автоматическое обновление отключается.
Ранее использовался cacheTime.
В современных версиях применяется gcTime.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
gcTime: 1000 * 60 * 10
})
Параметр определяет:
Если запрос больше никем не используется, запускается таймер garbage collection.
Автоматическое обновление при возврате на вкладку:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
refetchOnWindowFocus: true
})
| Значение | Поведение |
|---|---|
true |
Обновлять stale-запросы |
false |
Не обновлять |
"always" |
Всегда обновлять |
Периодический polling:
useQuery({
queryKey: ['stats'],
queryFn: fetchStats,
refetchInterval: 5000
})
Запрос выполняется каждые 5 секунд.
Дополнительно:
refetchIntervalInBackground: true
Обновление продолжится даже в неактивной вкладке.
Управление повторными попытками:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
retry: 3
})
retry: false
retry: 5
retry: (failureCount, error) => {
if (error.status === 404) {
return false
}
return failureCount < 3
}
Интервал между попытками:
retryDelay: 2000
Или функция:
retryDelay: attempt => {
return Math.min(1000 * 2 ** attempt, 30000)
}
select преобразует данные.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
select: users => {
return users.map(user => user.name)
}
})
select:
Временные данные до загрузки:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
placeholderData: []
})
initialData: []
Позволяет указать время актуальности initialData.
initialDataUpdatedAt: Date.now()
Используется при пагинации.
useQuery({
queryKey: ['posts', page],
queryFn: () => fetchPosts(page),
keepPreviousData: true
})
При смене страницы:
Дополнительные данные запроса:
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
meta: {
requiresAuth: true
}
})
meta часто применяется:
useMutation используется для операций изменения
данных.
Базовый синтаксис:
const mutation = useMutation({
mutationFn: createUser
})
const {
mutate,
mutateAsync,
data,
error,
isPending,
isSuccess
} = useMutation({
mutationFn: createUser
})
Функция изменения данных:
const createUser = async user => {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(user)
})
return response.json()
}
mutate({
name: 'Alex'
})
useMutation({
mutationFn: createUser,
onSuccess: () => {
console.log('Успех')
}
})
onError: error => {
console.error(error)
}
Срабатывает всегда:
onSettled: () => {
console.log('Завершено')
}
После мутации часто требуется обновление кеша.
const queryClient = useQueryClient()
useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['users']
})
}
})
Создание клиента:
const queryClient = new QueryClient()
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60000,
retry: 2
}
}
})
Поддерживаются:
| Раздел | Назначение |
|---|---|
queries |
Настройки useQuery |
mutations |
Настройки useMutation |
Инвалидация кеша:
queryClient.invalidateQueries({
queryKey: ['users']
})
Принудительное обновление:
queryClient.refetchQueries({
queryKey: ['users']
})
Удаление кеша:
queryClient.removeQueries({
queryKey: ['users']
})
Ручное обновление кеша:
queryClient.setQueryData(
['user', user.id],
user
)
Получение данных из кеша:
const user = queryClient.getQueryData([
'user',
userId
])
Поддерживает фильтрацию:
queryClient.invalidateQueries({
queryKey: ['posts']
})
Точное совпадение:
queryClient.invalidateQueries({
queryKey: ['posts'],
exact: true
})
Без exact:
['posts']
совпадает с:
['posts', 1]
['posts', 2]
['posts', 'featured']
Предварительная загрузка:
await queryClient.prefetchQuery({
queryKey: ['users'],
queryFn: fetchUsers
})
Комбинация кеша и загрузки:
await queryClient.ensureQueryData({
queryKey: ['users'],
queryFn: fetchUsers
})
const query = useQuery({
queryKey: ['posts', page],
queryFn: () => fetchPosts(page),
enabled: true,
staleTime: 60000,
gcTime: 300000,
retry: 3,
retryDelay: 1000,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
refetchOnMount: true,
refetchInterval: false,
select: data => data.items,
placeholderData: previousData,
meta: {
analytics: true
}
})
Повторный запрос после восстановления сети:
refetchOnReconnect: true
Поведение при повторном монтировании:
refetchOnMount: true
Варианты:
| Значение | Поведение |
|---|---|
true |
Обновлять stale-запрос |
false |
Не обновлять |
"always" |
Всегда обновлять |
Режим работы сети:
networkMode: 'online'
| Значение | Назначение |
|---|---|
online |
Стандартное поведение |
always |
Игнорировать offline |
offlineFirst |
Поддержка offline-first |
Оптимизация ререндеров:
notifyOnChangeProps: ['data', 'error']
Компонент будет обновляться только при изменении указанных полей.
Оптимизация ссылочной целостности:
structuralSharing: true
Позволяет уменьшать лишние рендеры за счёт повторного использования неизменённых частей объекта.
Проброс ошибок в Error Boundary:
throwOnError: true
Интеграция с React Suspense:
suspense: true
В этом режиме TanStack Query выбрасывает Promise во время загрузки.
Передача ошибок в React Error Boundary:
useErrorBoundary: true
const usersQuery = useQuery({
queryKey: ['users', filters],
queryFn: ({ queryKey }) => {
const [, currentFilters] = queryKey
return fetchUsers(currentFilters)
},
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 30,
retry: 2,
refetchOnWindowFocus: false,
keepPreviousData: true,
select: response => {
return response.data
},
enabled: filters.ready
})
Разработчик описывает состояние запроса, а не управляет им вручную.
Практически любое поведение меняется параметрами:
Большинство параметров можно комбинировать:
useQuery({
queryKey: ['stats'],
queryFn: fetchStats,
staleTime: 60000,
retry: 5,
refetchInterval: 10000,
refetchOnWindowFocus: false
})
queryKey: [Math.random()]
Создаёт бесконечные новые кеши.
Плохой вариант:
queryKey: ['user']
queryFn: () => fetchUser(userId)
Корректный:
queryKey: ['user', userId]
Нежелательно:
select: data => {
data.users.push(newUser)
return data
}
Опасный вариант:
enabled: userId
Если userId = 0, запрос не выполнится.
Корректнее:
enabled: userId !== undefined