Вступление
Разработчики 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%. Получите готовую архитектуру данных за считанные дни.







