Принципы работы кэша в RTK Query

Кэш в RTK Query построен как детерминированная система хранения результатов запросов, где каждая комбинация endpoint + аргументы формирует отдельную запись в хранилище. Такой подход исключает необходимость ручного управления состоянием загрузки данных и позволяет библиотеке автоматически контролировать жизненный цикл данных.

В основе кэша лежит понятие уникального ключа запроса. RTK Query формирует его из двух компонентов:

  • имени endpoint
  • сериализованных аргументов запроса

Каждый вызов useQuery или store.dispatch(api.endpoints.xxx.initiate()) приводит к вычислению этого ключа. Даже незначительное отличие в аргументах создает новую запись в кэше.

Это означает:

  • getUser({ id: 1 }) и getUser({ id: 2 }) — разные сущности кэша
  • порядок полей в объекте аргументов может влиять на результат сериализации
  • массивы и вложенные структуры участвуют в формировании ключа полностью

Система кэширования не пытается «понимать» семантику данных, она опирается исключительно на структурное сравнение входных параметров.

Хранение данных и структура cache slice

Внутренне RTK Query хранит данные в отдельном reducer-срезе, обычно называемом api. Его структура включает несколько уровней:

  • queries — данные конкретных запросов
  • mutations — состояние мутаций
  • provided / subscriptions — связи между запросами и компонентами

Каждая запись в queries содержит:

  • data — результат запроса
  • status — состояние загрузки (pending, fulfilled, rejected)
  • endpointName
  • requestId
  • fulfilledTimeStamp
  • error (если есть)

Эта структура позволяет одновременно хранить несколько версий одного endpoint с разными аргументами без конфликтов.

Подписки и управление временем жизни кэша

Одним из ключевых механизмов является система subscriptions. Каждый компонент, использующий хук запроса, создает подписку на конкретный cache key.

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

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

Когда последний подписчик размонтируется:

  • запускается таймер удаления записи
  • по истечении keepUnusedDataFor данные удаляются из кэша

Значение keepUnusedDataFor определяет, сколько времени данные остаются в памяти после потери всех подписок. Это предотвращает как преждевременное удаление, так и бесконечное накопление данных.

Дедупликация запросов

RTK Query предотвращает повторное выполнение одинаковых запросов, если:

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

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

Это достигается через:

  • requestId для каждого выполнения
  • реестр активных запросов
  • связывание нескольких подписок с одним промисом

Таким образом, даже при множественных вызовах одного endpoint сеть не перегружается дублирующими запросами.

Инвалидация и связь через теги

Кэш в RTK Query не является статичным. Он управляется через систему tag-based invalidation.

Каждый endpoint может:

  • предоставлять теги (providesTags)
  • инвалидировать теги (invalidatesTags)

Когда происходит мутация:

  1. RTK Query помечает связанные теги как устаревшие
  2. все query-запросы, предоставляющие эти теги, считаются невалидными
  3. запускается автоматический refetch

Пример логики:

  • getPosts предоставляет тег Posts
  • addPost инвалидирует тег Posts
  • все кэши getPosts пересчитываются

Важно, что инвалидируется не весь кэш, а только связанные записи, что делает систему точечной и эффективной.

Поведение при повторном монтировании

При повторном использовании одного и того же запроса RTK Query учитывает несколько факторов:

  • есть ли данные в кэше
  • устарели ли они по времени
  • активны ли подписки
  • включен ли refetchOnMountOrArgChange

Если данные присутствуют и считаются актуальными, библиотека:

  • возвращает кэш мгновенно
  • не выполняет новый запрос

Если данные устарели или принудительно настроено обновление:

  • выполняется фоновый refetch
  • UI продолжает отображать старые данные до завершения запроса

Keep unused data и сборка мусора

Система очистки кэша работает по принципу отложенного удаления. После исчезновения последнего подписчика:

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

Если в течение этого времени запрос снова становится активным:

  • очистка отменяется
  • данные продолжают использоваться

Такой подход снижает количество повторных сетевых запросов при частом монтировании/размонтировании компонентов.

Нормализация и отсутствие глубокой нормализации

В отличие от Redux Toolkit Entity Adapter, RTK Query не использует полноценную нормализацию данных.

Кэш устроен как:

  • «один запрос → один результат»

Это означает:

  • данные не разбиваются на сущности
  • отсутствует глобальный store сущностей
  • обновление происходит на уровне всего результата запроса

Преимущество такого подхода — простота и предсказуемость. Недостаток — невозможность автоматически синхронизировать одинаковые сущности между разными запросами без использования тегов или ручных механизмов.

Re-fetch стратегии и триггеры обновления

RTK Query поддерживает несколько механизмов повторного получения данных:

  • refetchOnMountOrArgChange
  • refetchOnFocus
  • refetchOnReconnect
  • polling через pollingInterval

Каждый из этих механизмов взаимодействует с кэшем независимо, но всегда опирается на один и тот же cache key.

Например:

  • при восстановлении сети (reconnect) проверяются все активные подписки
  • при фокусе окна инициируются только активные query-записи
  • polling работает даже при неизменных подписках

Состояние данных: свежесть и устаревание

Каждая запись кэша содержит метку времени fulfilledTimeStamp. Она используется для определения:

  • устарели ли данные
  • нужно ли обновление при новом использовании

RTK Query не использует сложную модель «stale-while-revalidate» как отдельный режим, но реализует его поведение через комбинацию:

  • немедленной отдачи кэша
  • фонового refetch при необходимости

Таким образом достигается баланс между скоростью интерфейса и актуальностью данных.

Связь кэша с мутациями

Мутации в RTK Query не только отправляют данные на сервер, но и напрямую влияют на кэш.

После успешной мутации возможны сценарии:

  • инвалидируются теги → связанные query обновляются
  • вручную обновляется кэш через updateQueryData
  • оптимистические обновления изменяют кэш до ответа сервера

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

Оптимистические обновления и кэш

При оптимистическом обновлении RTK Query:

  1. кэш изменяется до завершения запроса
  2. изменения применяются через временный patch
  3. при ошибке выполняется откат состояния

Это возможно благодаря тому, что кэш хранит не только данные, но и историю изменений через undo-логи внутри middleware механизма.

Разделение кэша по endpoint’ам

Каждый endpoint имеет собственное пространство внутри кэша. Это исключает конфликты между разными типами запросов, даже если они возвращают схожие структуры данных.

Например:

  • getUser
  • getUserPosts

будут храниться независимо, даже если оба возвращают объект пользователя.

Итоговая логика работы кэша

Внутренняя модель RTK Query объединяет несколько принципов:

  • строгая идентификация через endpoint + аргументы
  • автоматическое управление жизненным циклом данных
  • счетчики подписок вместо ручного контроля
  • теговая система для инвалидации
  • отложенная очистка памяти
  • отсутствие глубокой нормализации

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