Вступление
Разработчики React-приложений постоянно пишут однотипный код для работы с данными: useEffect, useState, reducers. Каждый новый экран — дублирование логики загрузки, кэширования и обновления. В результате 60–70% кода — бойлерплейт, который отвлекает от бизнес-логики. На одном проекте мы заменили Redux Thunk на TanStack Query и сократили код на 70%, а число багов упало в 3 раза.
TanStack Query (ранее React Query) — библиотека для управления серверным состоянием. Она берёт на себя кэширование, фоновые обновления, дедупликацию запросов, пагинацию, мутации с оптимистичными обновлениями и prefetching. Результат: объём кода для работы с данными сокращается на 60–70%, а количество багов уменьшается в 2–3 раза. Мы настраиваем TanStack Query с 2020 года, выполнили более 50 проектов. Гарантируем стабильную работу и экономию времени на 30–50%. Закажите настройку — получите готовую архитектуру данных за считанные дни.
| Подход | Бойлерплейт | Сложность | Масштабируемость |
|---|---|---|---|
| Redux Thunk | Высокий | Высокая | Средняя |
| RTK Query | Средний | Средняя | Высокая |
| TanStack Query | Низкий | Низкая | Высокая |
| SWR | Низкий | Низкая | Средняя |
Какие проблемы решает TanStack Query?
Проблема ручного управления кэшем: без библиотеки приходится вручную синхронизировать запросы, обрабатывать загрузку и ошибки. TanStack Query автоматически кэширует данные, дедуплицирует параллельные запросы и обновляет кэш при мутациях. Ещё одна боль — инвалидация кэша. С иерархическими ключами вы можете одной командой сбросить все связанные списки, не затрагивая детали.
Как настроить QueryClient с оптимальными параметрами?
QueryClient — центральный объект, управляющий всеми запросами. Его настройка влияет на производительность. Вот рекомендуемые значения:
| Параметр | По умолчанию | Рекомендуется | Зачем |
|---|---|---|---|
staleTime |
0 | 5 * 60 * 1000 | Данные считаются свежими 5 минут, не делается повторный запрос при каждом монтировании |
gcTime |
5 * 60 * 1000 | 10 * 60 * 1000 | Кэш хранится 10 минут после удаления ссылок (бывший cacheTime) |
retry |
3 | 2 | Попытки при ошибке — достаточно для временных сбоев |
refetchOnWindowFocus |
false | true | Автоматически обновлять данные при возвращении на вкладку |
Пример базовой инициализации:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
retry: 2,
refetchOnWindowFocus: true,
},
mutations: {
retry: 0,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
Если проект использует Next.js App Router, мы добавляем серверный prefetch через dehydrate и HydrationBoundary.
Почему query keys — ключ к эффективной инвалидации?
Query keys — уникальные идентификаторы запроса. Правильная структура ключей определяет, как кэш инвалидируется после мутаций. Мы используем иерархический подход:
export const queryKeys = {
products: {
all: ['products'] as const,
lists: () => [...queryKeys.products.all, 'list'] as const,
list: (filters: ProductFilters) => [...queryKeys.products.lists(), filters] as const,
details: () => [...queryKeys.products.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.products.details(), id] as const,
},
users: {
all: ['users'] as const,
me: () => [...queryKeys.users.all, 'me'] as const,
profile: (id: string) => [...queryKeys.users.all, 'profile', id] as const,
},
}
Благодаря этому можно инвалидировать все списки продуктов одной командой, не затрагивая детали.
Основные хуки: useQuery, useMutation, useInfiniteQuery
Базовый хук для получения данных — useQuery. Он возвращает состояние загрузки, ошибки и данные. Для критичного UI используйте useSuspenseQuery с React Suspense.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function useProducts(filters: ProductFilters) {
return useQuery({
queryKey: queryKeys.products.list(filters),
queryFn: () => api.get<Product[]>('/products', { params: filters }),
placeholderData: (prev) => prev,
})
}
export function useUpdateProduct() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateProductDto }) =>
api.patch<Product>(`/products/${id}`, data),
onMutate: async ({ id, data }) => {
await queryClient.cancelQueries({ queryKey: queryKeys.products.detail(id) })
const previous = queryClient.getQueryData<Product>(queryKeys.products.detail(id))
queryClient.setQueryData(queryKeys.products.detail(id), (old: Product) => ({ ...old, ...data }))
return { previous }
},
onError: (_, { id }, context) => {
if (context?.previous) {
queryClient.setQueryData(queryKeys.products.detail(id), context.previous)
}
},
onSettled: (_, __, { id }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.products.detail(id) })
},
})
}
Для бесконечной прокрутки — useInfiniteQuery:
export function useInfiniteProducts(filters: Omit<ProductFilters, 'page'>) {
return useInfiniteQuery({
queryKey: queryKeys.products.list(filters),
queryFn: ({ pageParam }) =>
api.get<PaginatedResponse<Product>>('/products', { params: { ...filters, page: pageParam, pageSize: 20 } }),
initialPageParam: 1,
getNextPageParam: (lastPage) => (lastPage.hasNextPage ? lastPage.page + 1 : undefined),
})
}
Для простой пагинации используйте keepPreviousData в placeholderData.
Как реализовать оптимистичные обновления без боли?
Оптимистичное обновление — интерфейс меняется до ответа сервера. TanStack Query делает это просто: в onMutate обновляем кэш, в onError откатываем. Пример выше с useUpdateProduct показывает эту технику. Пользователь видит мгновенную обратную связь, а при ошибке данные возвращаются к исходному состоянию.
Prefetching и SSR с Next.js
Prefetching загружает данные до перехода на страницу. Например, при наведении на ссылку:
function ProductLink({ id }: { id: string }) {
const queryClient = useQueryClient()
return (
<Link to={`/products/${id}`}
onMouseEnter={() => {
queryClient.prefetchQuery({
queryKey: queryKeys.products.detail(id),
queryFn: () => api.get<Product>(`/products/${id}`),
staleTime: 60_000,
})
}}
>Перейти</Link>
)
}
Для SSR в Next.js App Router используем серверный prefetchQuery и HydrationBoundary:
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'
export default async function ProductsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: queryKeys.products.lists(),
queryFn: () => fetchProductsServer(),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductList />
</HydrationBoundary>
)
}
Подробнее — в официальной документации TanStack Query.
Что входит в нашу услугу
Мы настраиваем TanStack Query под ваш проект:
- Установка и конфигурация QueryClient с учётом особенностей вашего API.
- Проектирование иерархии query keys для удобной инвалидации.
- Написание кастомных хуков для всех endpoints: useQuery, useMutation, useInfiniteQuery.
- Реализация оптимистичных обновлений для форм (лайки, комментарии, редактирование).
- Настройка prefetching и интеграция с SSR/Next.js.
- Подключение DevTools для отладки.
- Документация и обучение команды.
Процесс работы:
- Аналитика — аудит текущего стека и API, определение точек интеграции.
- Проектирование — схемы query keys, выбор стратегии кэширования.
- Реализация — написание хуков, тестирование мутаций, оптимизация.
- Тестирование — проверка инвалидации, авторизации, edge-кейсов.
- Деплой — развёртывание, мониторинг через DevTools.
Сроки: от 2 до 5 дней в зависимости от количества Endpoints. Стоимость рассчитывается индивидуально. Экономия на дальнейшей поддержке окупает затраты в течение нескольких недель.
Свяжитесь с нами для консультации — мы поможем внедрить TanStack Query и сократить время разработки на 30–50%. Получите готовую архитектуру данных за считанные дни.







