В основе работы TanStack Query лежат два глобальных хранилища:
QueryCache — кэш запросовMutationCache — кэш мутацийОни являются частью экземпляра QueryClient и отвечают за
централизованное хранение состояния данных, подписок, статусов, ошибок,
таймеров и жизненного цикла запросов.
Структура выглядит следующим образом:
QueryClient
├── QueryCache
│ ├── Query
│ ├── Query
│ └── Query
│
└── MutationCache
├── Mutation
├── Mutation
└── Mutation
Каждый вызов useQuery() создает или использует объект
Query.
Каждый вызов useMutation() создает объект
Mutation.
QueryCache хранит все query-запросы приложения.
Именно здесь находятся:
Пример:
const queryClient = new QueryClient()
console.log(queryClient.getQueryCache())
Каждый query внутри кэша содержит:
{
queryKey,
queryHash,
state,
observers,
gcTimeout,
options
}
Оригинальный ключ запроса:
['posts', 5]
Внутренний сериализованный hash:
'["posts",5]'
Именно по hash TanStack Query определяет уникальность запроса.
Содержит текущее состояние query.
Пример структуры:
{
data,
error,
status,
fetchStatus,
dataUpdatedAt,
errorUpdatedAt
}
Массив подписчиков.
Обычно это компоненты React, использующие
useQuery().
При первом вызове:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
TanStack Query:
Проверяет наличие query в QueryCache
Если query отсутствует:
Запускается fetch
Если другой компонент использует:
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
})
новый запрос не создается.
Оба компонента подписываются на один Query.
Внутренне Query является state machine.
Упрощенная схема:
idle
↓
pending
↓
success
↓
stale
↓
refetching
Либо:
pending
↓
error
const queryCache = queryClient.getQueryCache()
Поиск query:
const query = queryCache.find({
queryKey: ['posts']
})
Получение всех query:
const queries = queryCache.getAll()
QueryCache поддерживает глобальные подписки.
Пример:
const unsubscribe = queryCache.subscribe((event) => {
console.log(event)
})
Срабатывает при добавлении query.
{
type: 'added',
query
}
Срабатывает при удалении query.
{
type: 'removed',
query
}
Срабатывает при обновлении query.
{
type: 'updated',
query,
action
}
queryCache.subscribe((event) => {
console.log(
event.type,
event.query.queryKey
)
})
queryCache.subscribe((event) => {
if (event.type === 'updated') {
analytics.track('query_updated')
}
})
queryCache.subscribe((event) => {
console.log(event.query.state)
})
TanStack Query не хранит query бесконечно.
Если query:
gcTimeто он удаляется из QueryCache.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
gcTime: 1000 * 60 * 5
})
Через 5 минут после потери подписчиков query будет удален.
Есть хотя бы один observer.
Component A
↓
useQuery()
↓
Query observer exists
Подписчиков больше нет.
No components
↓
No observers
↓
Inactive query
После этого запускается countdown garbage collection.
staleTime влияет только на freshness query.
Он не удаляет query из кэша.
useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 60000
})
В течение минуты query считается fresh.
При invalidation QueryCache помечает query как stale.
queryClient.invalidateQueries({
queryKey: ['posts']
})
Внутренне происходит:
Query.state.isInvalidated = true
Полное удаление query из QueryCache.
queryClient.removeQueries({
queryKey: ['posts']
})
После удаления:
Сброс query в исходное состояние.
queryClient.resetQueries({
queryKey: ['posts']
})
Данные очищаются, но query остается в QueryCache.
Полная очистка QueryCache.
queryClient.clear()
Удаляются:
Компоненты React не работают напрямую с Query.
Между ними находится QueryObserver.
Схема:
Component
↓
QueryObserver
↓
Query
↓
QueryCache
Observer:
TanStack Query использует notifyManager для оптимизации
обновлений.
Вместо множества re-render:
Query update
Query update
Query update
TanStack Query делает batching:
Batch
├── update
├── update
└── update
Это уменьшает нагрузку на React.
MutationCache хранит все мутации приложения.
Каждый useMutation() создает объект Mutation.
Каждая mutation содержит:
{
mutationId,
state,
options,
observers
}
Пример:
{
status,
data,
error,
variables,
submittedAt
}
Мутация еще не запускалась.
Мутация выполняется.
Успешное завершение.
Ошибка выполнения.
const mutation = useMutation({
mutationFn: createPost
})
После вызова:
mutation.mutate(data)
создается Mutation object в MutationCache.
В отличие от QueryCache, мутации обычно живут недолго.
После завершения они:
const mutationCache =
queryClient.getMutationCache()
mutationCache.subscribe((event) => {
console.log(event)
})
Создание mutation.
Удаление mutation.
Изменение состояния mutation.
Одно из главных применений MutationCache — централизованная обработка ошибок.
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: (error) => {
console.error(error)
}
})
})
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (data) => {
console.log(data)
}
})
})
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSettled: () => {
console.log('finished')
}
})
})
| QueryCache | MutationCache |
|---|---|
| Хранит query | Хранит mutation |
| Долгоживущий | Краткоживущий |
| Переиспользуется | Обычно одноразовый |
| Кэширует данные | Не предназначен для кэширования |
| Поддерживает stale/fresh | Нет stale-механизма |
| Использует queryKey | Использует mutationId |
Основной сценарий:
Mutation
↓
Server update
↓
invalidateQueries()
↓
QueryCache refresh
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: updatePost,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['posts']
})
}
})
TanStack Query не знает:
Поэтому разработчик сам управляет синхронизацией.
Mutation может обновлять QueryCache вручную.
queryClient.setQueryData(
['posts', id],
(oldData) => ({
...oldData,
title: 'New title'
})
)
Внутренне:
Mutation success
↓
QueryCache.find()
↓
Query.state.data update
↓
Observers notified
↓
React re-render
TanStack Query Devtools напрямую работают с QueryCache.
Они отображают:
Devtools также показывают:
Упрощенная схема:
useQuery()
↓
QueryObserver
↓
QueryCache.find()
↓
Query exists?
├── yes → subscribe
└── no
↓
create Query
↓
fetch()
↓
update state
↓
notify observers
mutate()
↓
create Mutation
↓
MutationCache.add()
↓
execute mutationFn
↓
success/error
↓
callbacks
↓
invalidateQueries()
Именно QueryCache обеспечивает:
Без QueryCache TanStack Query превращается в обычный fetch wrapper.
Прямое взаимодействие с QueryCache используется редко, но важно в:
MutationCache обычно используют для: