Архитектура TanStack Query построена вокруг централизованных
сущностей QueryCache и MutationCache, через
которые проходит весь поток асинхронных операций. Это позволяет
реализовывать поведение, аналогичное middleware и interceptors, без
встроенной классической системы перехватчиков уровня Axios или
Express.
Вместо единой точки перехвата запросов библиотека предоставляет несколько уровней расширения поведения:
QueryClientqueryCache и
mutationCachequeryFn и mutationFnsetQueryDefaults и
setMutationDefaultsКаждый уровень решает свою задачу и формирует многоступенчатую модель обработки запроса.
Любой запрос в TanStack Query проходит последовательность этапов:
queryFn или mutationFnMiddleware-подход в данной модели реализуется через вмешательство в каждый этап, но чаще всего — через перехват на уровне функций и событий кеша.
Наиболее простой способ задать “перехватывающее” поведение —
использование QueryClient с дефолтными опциями.
import { QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
if (error.status === 401) return false
return failureCount < 3
},
staleTime: 1000 * 30,
},
},
})
Здесь retry выступает как middleware-логика, которая
перехватывает ошибку до повторного запроса.
Аналогично можно централизовать обработку ошибок:
defaultOptions: {
queries: {
onError: (error) => {
console.log('Global query error:', error)
}
}
}
Такой подход задаёт единый слой поведения для всех запросов.
Более точечный механизм — настройка поведения по ключу query.
queryClient.setQueryDefaults(['users'], {
queryFn: fetchUsers,
staleTime: 1000 * 60,
retry: 2,
})
Фактически это аналог middleware, привязанного к маршруту:
['users'] — аналог route patternqueryFn — основной handlerДля мутаций:
queryClient.setMutationDefaults(['updateUser'], {
mutationFn: updateUser,
retry: 1,
onError: (err) => {
console.error('Update failed', err)
},
})
Это позволяет централизовать бизнес-логику без дублирования в компонентах.
Низкоуровневый механизм middleware реализуется через подписку на события кеша.
queryClient.getQueryCache().subscribe((event) => {
console.log(event.type, event.query?.queryKey)
})
События включают:
addedremovedupdatedobserverAddedobserverRemovedЭтот уровень ближе всего к настоящему interceptor pipeline, так как позволяет реагировать на изменения состояния запроса независимо от источника.
Для мутаций используется отдельный кеш:
queryClient.getMutationCache().subscribe((event) => {
console.log('mutation event:', event.type)
})
Это позволяет централизованно отслеживать:
Пример обработки глобальных side effects:
queryClient.getMutationCache().subscribe((event) => {
if (event.type === 'updated') {
const mutation = event.mutation
if (mutation.state.status === 'error') {
showToast('Mutation failed')
}
}
})
Самый гибкий способ реализовать interceptors — обёртка над
queryFn.
const withAuth = (fn) => {
return async (context) => {
const token = localStorage.getItem('token')
return fn({
...context,
headers: {
...context.headers,
Authorization: `Bearer ${token}`,
},
})
}
}
Использование:
const fetchUsers = withAuth(async ({ headers }) => {
const res = await fetch('/api/users', { headers })
if (!res.ok) throw res
return res.json()
})
Такой подход реализует классическую middleware-цепочку:
Несколько middleware можно комбинировать:
const compose = (...fns) => {
return (initial) =>
fns.reduceRight((acc, fn) => fn(acc), initial)
}
Пример:
const withAuth = (fn) => async (ctx) => {
const token = 'abc'
return fn({ ...ctx, token })
}
const withLogging = (fn) => async (ctx) => {
console.log('Request started')
const result = await fn(ctx)
console.log('Request finished')
return result
}
const fetchUsers = compose(
withLogging,
withAuth
)(async ({ token }) => {
const res = await fetch('/api/users', {
headers: { Authorization: token },
})
return res.json()
})
Такой подход полностью повторяет middleware pipeline из серверных фреймворков.
TanStack Query активно использует side effects через:
onSuccessonErroronSettledЭти колбэки формируют встроенную систему перехвата результата.
useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
onSuccess: (data) => {
cacheUsers(data)
},
onError: (error) => {
reportError(error)
},
})
Важно, что эти interceptors локальны, но могут быть вынесены в глобальные defaults.
Одной из ключевых задач middleware является централизованная обработка ошибок.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
onError: (error) => {
if (error.status === 401) {
logout()
}
if (error.status >= 500) {
sendToMonitoring(error)
}
},
},
},
})
Это создаёт глобальный interception layer, аналогичный HTTP interceptor в Axios.
Метод fetchQuery позволяет внедрять middleware-логику до
попадания данных в кеш.
await queryClient.fetchQuery({
queryKey: ['users'],
queryFn: async () => {
console.log('before fetch')
const data = await fetch('/api/users').then(r => r.json())
console.log('after fetch')
return data
},
})
Этот слой важен тем, что:
Система инвалидирования кеша может выступать как реактивный interceptor.
queryClient.invalidateQueries({
queryKey: ['users'],
})
Это запускает цепочку:
Можно рассматривать invalidate как “сигнал middleware”, запускающий downstream обработку.
Создание кастомного клиента позволяет внедрять централизованное поведение:
class AppQueryClient extends QueryClient {
constructor(options) {
super(options)
this.getQueryCache().subscribe((event) => {
this.log(event)
})
}
log(event) {
if (process.env.NODE_ENV !== 'production') {
console.log('[Query event]', event)
}
}
}
Такой подход превращает клиент в middleware-container.
В TanStack Query можно выделить уровни аналогичные middleware stack:
Каждый уровень дополняет предыдущий и не заменяет его.
Несмотря на гибкость, существуют ограничения:
queryFn остаётся главным источником контроляПоэтому архитектура всегда остаётся гибридной: декларативной + функциональной.
В реальных приложениях чаще всего используется комбинация:
queryFn wrapper для auth и трансформацийsetQueryDefaults для бизнес-логикиonError для глобальной обработки ошибокqueryCache.subscribe для мониторингаmutationCache.subscribe для сайд-эффектовЭта комбинация формирует устойчивую middleware-систему поверх реактивного кеша TanStack Query.