Архитектура библиотеки

TanStack Query построена вокруг идеи отделения серверного состояния от UI и управления им через единый, централизованный слой кеша. В основе лежит не просто библиотека для загрузки данных, а полноценная архитектурная модель, в которой данные, их жизненный цикл и синхронизация с сервером рассматриваются как отдельная система.


Ядро системы: QueryClient

Центральным элементом архитектуры является QueryClient. Это объект, который координирует все операции с данными:

  • хранение кеша запросов
  • управление инвалидацией
  • выполнение фоновых перезапросов
  • контроль мутаций
  • управление подписками на изменения данных

QueryClient выступает как единая точка правды для всего серверного состояния в приложении. Все hooks и утилиты TanStack Query работают через него.

Внутри QueryClient содержится несколько ключевых подсистем:

  • QueryCache — хранит результаты запросов
  • MutationCache — хранит состояние мутаций
  • Default Options Resolver — управляет глобальными настройками поведения запросов

QueryCache как основа хранения данных

QueryCache представляет собой структурированное хранилище, где каждый запрос идентифицируется уникальным ключом (queryKey).

Каждый элемент кеша — это объект Query, который содержит:

  • состояние загрузки (loading, error, success)
  • данные (data)
  • временные метки (dataUpdatedAt)
  • подписчиков (components, hooks)
  • конфигурацию запроса

Ключевой принцип: один и тот же queryKey всегда ссылается на один и тот же источник данных в кеше.

QueryCache работает как реактивный слой:

  • изменение данных → уведомление подписчиков
  • изменение статуса → триггер ререндера UI
  • устаревание данных → автоматический refetch

Жизненный цикл Query

Каждый запрос в TanStack Query проходит строго определённый жизненный цикл.

1. Инициализация

При первом вызове useQuery создаётся Query объект (если его нет в кеше). Он регистрируется в QueryCache.

2. Подписка

Компонент подписывается на Query. Подписка означает, что любые изменения состояния Query будут вызывать обновление UI.

3. Запрос данных

Если данные отсутствуют или устарели, запускается функция queryFn. Она может возвращать Promise.

4. Состояние загрузки

Query переходит в состояние loading. Все подписчики получают обновление.

5. Успешное завершение

При успешном ответе:

  • данные записываются в кеш
  • обновляется timestamp
  • состояние меняется на success
  • подписчики получают новые данные

6. Ошибка

При ошибке:

  • состояние меняется на error
  • ошибка сохраняется в Query
  • активируются retry-стратегии (если включены)

Гранулярность кеша и ключи запросов

Архитектура TanStack Query полностью опирается на структурированные ключи.

queryKey — это не строка, а массив, который позволяет создавать иерархию:

['users', 42, 'posts']

Такой подход даёт:

  • строгую изоляцию данных
  • возможность частичной инвалидации
  • предсказуемое переиспользование кеша

Кеш организован как дерево, где каждый уровень ключа добавляет детализацию.


Подписочная модель и реактивность

TanStack Query использует модель подписок вместо глобального state management.

Каждый useQuery:

  • регистрируется как observer Query
  • получает обновления только от своего Query
  • не зависит от глобального rerender механизма

Это позволяет:

  • минимизировать лишние ререндеры
  • изолировать компоненты
  • масштабировать количество запросов без деградации UI

Подписка работает через внутренний event emitter QueryCache.


Stale Time и управление свежестью данных

В архитектуре важно различие между:

  • кешированными данными
  • устаревшими (stale)
  • неиспользуемыми

Каждый Query имеет параметры:

  • staleTime — период, в течение которого данные считаются свежими
  • cacheTime — время жизни неиспользуемого кеша

Механизм работает так:

  • данные могут оставаться в кеше
  • но считаться устаревшими
  • и автоматически перезапрашиваться при активности

Это позволяет разделить:

  • хранение данных
  • и их актуальность

Garbage Collection и освобождение памяти

Query, к которым нет подписчиков, не удаляются мгновенно. Вместо этого используется отсроченная очистка:

  • Query остаётся в кеше
  • запускается таймер cacheTime
  • если за это время нет подписчиков — Query удаляется

Такой подход предотвращает:

  • утечки памяти
  • постоянное пересоздание одинаковых запросов

MutationCache и побочные операции

MutationCache управляет изменениями данных на сервере (POST, PUT, DELETE).

Каждая мутация содержит:

  • статус выполнения
  • payload запроса
  • результаты
  • ошибки
  • callbacks (onSuccess, onError, onSettled)

Мутабельные операции не заменяют QueryCache напрямую. Вместо этого они:

  • инициируют side effects
  • триггерят invalidation
  • обновляют связанные queries

Инвалидация как механизм синхронизации

Инвалидация — ключевой архитектурный механизм синхронизации.

При вызове invalidateQueries:

  • соответствующие Query помечаются как stale
  • активные подписчики инициируют refetch
  • устаревшие данные не удаляются, но помечаются

Важно, что инвалидация не равна удалению. Это логическая метка, а не физическая операция.


Фоновое обновление данных

TanStack Query поддерживает background refetch, встроенный в архитектуру QueryObserver.

Обновление может происходить:

  • при фокусе окна
  • при восстановлении сети
  • по таймеру refetchInterval
  • при маунте компонента

Это реализовано через систему глобальных event listeners, которые связываются с QueryClient.


QueryObserver как мост между кешем и UI

QueryObserver — скрытый слой, который связывает:

  • QueryCache (данные)
  • UI hooks (useQuery)

Он отвечает за:

  • отслеживание изменений Query
  • управление подписками
  • оптимизацию ререндеров
  • агрегацию состояний

Фактически useQuery — это тонкая обёртка над QueryObserver.


Согласованность данных и race conditions

Архитектура учитывает конкурентные запросы:

  • несколько одинаковых запросов не дублируются
  • используется deduplication layer
  • последний успешный ответ побеждает (unless stale)

Также применяются механизмы:

  • отмены запросов
  • игнорирования устаревших ответов
  • timestamp-based validation

Изоляция серверного состояния от UI

Ключевой архитектурный принцип:

  • UI не хранит серверные данные
  • UI только подписывается на QueryCache
  • бизнес-логика отделена от представления

Это создаёт слой абстракции:

UI → QueryObserver → QueryCache → QueryClient → Server


Иерархия компонентов архитектуры

Вся система можно представить как слоистую модель:

  • UI слой — React/Vue/Solid компоненты
  • Observer слой — QueryObserver
  • Core слой — QueryClient
  • Cache слой — QueryCache и MutationCache
  • Transport слой — queryFn, mutationFn (HTTP, fetch, axios)

Каждый слой строго ограничивает ответственность.


Оптимизация работы кеша

Архитектура включает несколько оптимизаций:

  • дедупликация одинаковых запросов
  • батчинг обновлений подписчиков
  • ленивое создание Query
  • мемоизация ключей
  • минимизация пересоздания observer-объектов

Событийная модель внутри QueryClient

QueryClient работает как event-driven система:

  • queryAdded
  • queryUpdated
  • queryRemoved
  • mutationAdded
  • focus/refetch events

Эти события используются для синхронизации всех подсистем.


Итоговая модель поведения системы

TanStack Query функционирует как распределённый кеш с реактивной моделью обновления, где:

  • данные централизованы в QueryCache
  • изменения распространяются через подписки
  • синхронизация выполняется через QueryObserver
  • серверное состояние отделено от UI
  • мутации управляют изменениями через инвалидацию

Архитектура построена так, чтобы минимизировать прямое управление данными и перенести всю сложность в контролируемый слой кеширования и событийной синхронизации