TanStack Query (React Query): руководство по эффективному извлечению данных в React

Что такое TanStack Query (коротко)
TanStack Query — это клиентская библиотека для получения, кеширования и синхронизации серверных данных в React. Она абстрагирует большую часть обычного boilerplate вокруг fetch/axios, предоставляет декларативные хуки и готовые стратегии для повторных попыток, рефетча и оптимистичных обновлений.
Определение: useQuery — хук для чтения/кеширования, useMutation — хук для модификации данных (POST/PUT/DELETE). Ключевая сущность — QueryClient, который управляет состоянием кеша и политиками.
Установка и первоначальная настройка
Установите библиотеку через npm или yarn:
npm i @tanstack/react-queryили
yarn add @tanstack/react-queryПосле установки оберните корневой компонент приложения в QueryClientProvider и создайте экземпляр QueryClient. Пример корректной и минимальной инициализации (React 18+):
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')).render(
)Важно: QueryClientProvider должен быть выше компонентов, которые пользуются хуками tanstack/react-query.
Основные понятия (одной строкой)
- QueryKey — уникальный идентификатор запроса. Может быть строкой, массивом или объектом.
- QueryFn — функция, возвращающая Promise с данными.
- staleTime — время (мс), в течение которого данные считаются «свежими».
- cacheTime — время (мс), по истечении которого неиспользуемый кеш удаляется.
useQuery — получение и кеширование данных
useQuery предназначен для чтения данных и автоматического кеширования. Он принимает объект с параметрами или упрощённый синтаксис. Пример с axios:
import React from 'react'
import axios from 'axios'
import { useQuery } from '@tanstack/react-query'
function Home() {
const postQuery = useQuery({
queryKey: ['posts'],
queryFn: async () => {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
return response.data
},
staleTime: 1000 * 60, // данные считаются свежими 1 мин
cacheTime: 1000 * 60 * 5, // кеш хранится 5 мин если никто не использует
})
if (postQuery.isLoading) return Загрузка…
if (postQuery.isError) return Ошибка при загрузке данных
return (
Home
{postQuery.data.map(item => (
{item.title}
))}
)
}
export default HomeЧто важно знать:
- postQuery возвращает состояние: isLoading, isError, isSuccess, data, error и др.
- queryKey обязателен для идентификации и инвалидации.
- queryFn получает аргумент с полем queryKey: useful для динамических запросов.
Пример доступа к queryKey внутри queryFn:
useQuery({
queryKey: ['posts', { userId: 2 }],
queryFn: async ({ queryKey }) => {
// queryKey === ['posts', { userId: 2 }]
const [, params] = queryKey
const resp = await fetch(`https://api.example.com/posts?userId=${params.userId}`)
return resp.json()
}
})Управление «устаревшими» данными и рефетчем
TanStack Query по умолчанию делает рефетч при фокусе окна и при восстановлении сети. Дополнительные настройки:
- staleTime — время в миллисекундах, пока данные считаются свежими. После этого при обращении может инициироваться рефетч.
- refetchInterval — периодический рефетч (мс). Можно поставить false для отключения.
- refetchOnWindowFocus — рефетчать при фокусе окна (true/false).
- cacheTime — сколько держать кеш, если на него нет подписчиков.
Примеры:
useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
staleTime: 1000 * 30, // 30 сек
refetchInterval: 1000 * 60 * 5, // каждые 5 минут
refetchOnWindowFocus: true,
})Important: слишком частый рефетч ухудшит производительность и расход трафика. Настраивайте под характер данных (часто меняются / редко меняются).
useMutation — создание и изменение данных
useMutation используется для POST/PUT/DELETE операций. Он не кеширует автоматически данные (это мутации), но позволяет запускать побочные эффекты (invalidateQueries) и реализовывать оптимистичные обновления.
Полный пример компонента AddPost с useMutation и оптимистичным обновлением:
import React from 'react'
import axios from 'axios'
import { useMutation, useQueryClient } from '@tanstack/react-query'
function AddPost() {
const [post, setPost] = React.useState({ title: '' })
const queryClient = useQueryClient()
const newPostMutation = useMutation({
mutationFn: async (newPost) => {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost)
return response.data
},
onMutate: async (newPost) => {
// Отключаем автоматический рефетч и делаем оптимистичное обновление кеша
await queryClient.cancelQueries(['posts'])
const previous = queryClient.getQueryData(['posts'])
queryClient.setQueryData(['posts'], old => [...(old || []), { id: Date.now(), ...newPost }])
return { previous }
},
onError: (err, newPost, context) => {
// Откат в случае ошибки
queryClient.setQueryData(['posts'], context.previous)
},
onSettled: () => {
// После завершения (успех или ошибка) инвалидация кеша для получения актуальных данных
queryClient.invalidateQueries(['posts'])
}
})
function handleChange(e) {
setPost(prev => ({ ...prev, [e.target.name]: e.target.value }))
}
async function handleSubmit(e) {
e.preventDefault()
newPostMutation.mutate({ title: post.title })
setPost({ title: '' })
}
return (
)
}
export default AddPostПояснения:
- onMutate — используется для оптимистичного обновления кеша.
- onError — откат изменений при ошибке.
- onSettled/onSuccess — инвалидация или обновление других запросов.
Распространённые опции и паттерны
- retry: число повторных попыток (по умолчанию 3). Можно поставить false.
- enabled: условное включение запроса (например, только когда есть id).
- select: преобразование данных перед выдачей компоненту.
- placeholderData: временные данные, пока основная загрузка не вернулась.
Пример условного запроса:
useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
enabled: !!userId,
})Альтернативы и когда React Query не подходит
- SWR (Vercel) — альтернативная библиотека с похожими целями, проще по API в некоторых сценариях.
- Apollo Client — если основная нагрузка — GraphQL; предоставляет более тесную интеграцию с GraphQL‑фичами (фрагменты, cache policies).
- Простые fetch/axios + локальный стейт — подходит для очень маленьких приложений и простых однократных запросов.
Когда не стоит использовать TanStack Query:
- Если приложение не делает запросов к серверу (статический сайт без обновлений).
- Если нужна экстремально тонкая оптимизация для специфичных протоколов (требуется специальный клиент).
Ментальные модели и эвристики
- Кеш = источник правды для UI (read from cache, write via mutations + invalidate).
- Никаких «глобальных» fetch-циклов вручную: хуки управляют жизненным циклом.
- Делайте данные «достаточно свежими»: для критичных данных ставьте короткий staleTime; для редко меняющихся — длинный.
Правило большого пальца: если данные отображаются в несколько мест — используйте queryKey и кеш, чтобы избежать дублированных fetch.
Практическая методология внедрения (чеклист)
- Установить @tanstack/react-query и добавить QueryClientProvider.
- Определить ключи запросов (queryKey) и договориться о формате (например, [‘posts’, { userId }]).
- Для каждого запроса создать useQuery с подходящими staleTime, cacheTime.
- Для мутаций использовать useMutation; для атомарных изменений — onMutate/onError/onSettled.
- Написать тесты/кейс‑тесты для успешного запроса, ошибки и повторных попыток.
- Наблюдать поведение в проде: трафик, частота рефетчей, UX.
Критерии приёмки
- Данные отображаются при первом рендере и при сетевых изменениях.
- При создании/удалении/обновлении данных UI корректно отражает состояние (оптимистично или после успешного ответа).
- Ошибки показываются пользователю и логируются.
- Нет лишних повторных запросов при переходах между страницами.
Сравнение: useQuery vs useMutation (быстрая шпаргалка)
- useQuery: чтение, кеширование, авто‑рефетч. Возвращает { data, isLoading, isError, isSuccess }.
- useMutation: запись/изменение, не кеширует автоматически. Позволяет оптимистичные обновления и откаты.
Совместимость и миграция
- TanStack Query поддерживает React 16.8+ (хуки). Для Next.js используйте подходы SSR/SSG: hydrate состояние на клиенте через dehydrate/hydrate helper из @tanstack/react-query.
- При миграции с SWR проверьте различия в lifecycle (рефетч при фокусе по умолчанию) и API.
Мини‑чеклист безопасности и приватности
- Не храните в кеше чувствительные личные данные дольше, чем нужно; настраивайте cacheTime.
- Для данных, подлежащих удалению по GDPR, инвалируйте и удаляйте их из кеша на стороне клиента при запросе на удаление.
Быстрая шпаргалка (cheat sheet)
- Создать клиент: new QueryClient()
- Провайдер:
- Чтение: useQuery({ queryKey, queryFn, staleTime, cacheTime })
- Запись: useMutation({ mutationFn, onMutate, onError, onSettled })
- Управление кешем: useQueryClient().invalidateQueries([‘key’])
Пример диаграммы принятия решения (Mermaid)
flowchart TD
A[Нужно получить данные?] -->|Да| B{Есть уникальный queryKey?}
B -->|Да| C[Использовать useQuery]
B -->|Нет| D[Определить ключ 'напр. ['resource', id]']
A -->|Нет| E[Нет запросов — не использовать React Query]
C --> F{Нужно изменить данные?}
F -->|Да| G[Использовать useMutation + invalidateQueries]
F -->|Нет| H[Обычный read-only flow]Примеры типичных ошибок и как их исправить
- Проблема: бесконечные рефетчи — проверьте refetchInterval и callback внутри queryFn, не меняющий queryKey каждую итерацию.
- Проблема: stale данные после мутации — используйте invalidateQueries или onSuccess для обновления кеша.
- Проблема: запросы выполняются слишком часто — увеличьте staleTime или отключите refetchOnWindowFocus.
Резюме
TanStack Query значительно упрощает работу с асинхронными данными в React: он управляет кешем, рефетчем, стратегиями повторных попыток и даёт мощные точки расширения (optimistic updates, query invalidation). При грамотной конфигурации вы получаете более отзывчивый UI и меньше сетевого шума.
Notes: начните с простых настроек по умолчанию и добавляйте тонкую настройку staleTime/refetchInterval по мере понимания характера ваших данных.