Система тегов для управления кэшем

Система тегов в RTK Query является центральным механизмом декларативного управления инвалидированием кэша. Она устраняет необходимость вручную отслеживать зависимости между запросами и мутациями, заменяя императивную логику обновления данных на описательную модель «что связано с чем».

В основе лежит идея: каждый запрос может «помечать» свои данные тегами, а мутации — «сбрасывать» эти теги, автоматически инициируя повторные запросы там, где это необходимо.


Базовая концепция тегов

Тег в RTK Query — это метка, которая ассоциируется с данными, полученными через endpoint.

Система работает по двум основным направлениям:

  • providesTags — какие теги «выдаются» запросом
  • invalidatesTags — какие теги «сбрасываются» мутацией

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


Объявление tagTypes

Перед использованием тегов их необходимо зарегистрировать в API-сервисе:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Users', 'Posts', 'Comments'],
  endpoints: (builder) => ({
    // endpoints
  }),
});

Значение tagTypes

tagTypes задаёт ограниченный набор допустимых тегов. Это важно по нескольким причинам:

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

Если тег не объявлен в tagTypes, он не будет корректно участвовать в системе кэш-инвалидации.


providesTags: привязка данных к тегам

providesTags используется в query-endpoint и описывает, какие данные возвращает запрос.

Базовый пример

getUsers: builder.query({
  query: () => '/users',
  providesTags: ['Users'],
});

В этом случае весь список пользователей ассоциируется с тегом Users.


Динамические теги с идентификаторами

Чаще всего требуется более точная гранулярность — управление не только коллекцией, но и отдельными сущностями.

getUserById: builder.query({
  query: (id) => `/users/${id}`,
  providesTags: (result, error, id) => [
    { type: 'Users', id },
  ],
});

Особенности:

  • тег становится объектом { type, id }
  • позволяет инвалидировать конкретную сущность
  • не затрагивает остальные элементы коллекции

Множественные теги из результата

RTK Query позволяет генерировать теги на основе ответа API:

getUsers: builder.query({
  query: () => '/users',
  providesTags: (result) =>
    result
      ? [
          ...result.map(({ id }) => ({ type: 'Users', id })),
          { type: 'Users', id: 'LIST' },
        ]
      : [{ type: 'Users', id: 'LIST' }],
});

Логика LIST-тега

Использование { id: 'LIST' } позволяет:

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

invalidatesTags: механизм обновления данных

Мутации используют invalidatesTags для объявления, какие данные стали неактуальными.

Пример добавления пользователя

addUser: builder.mutation({
  query: (body) => ({
    url: '/users',
    method: 'POST',
    body,
  }),
  invalidatesTags: [{ type: 'Users', id: 'LIST' }],
});

После успешного выполнения мутации:

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

Инвалидация конкретных сущностей

deleteUser: builder.mutation({
  query: (id) => ({
    url: `/users/${id}`,
    method: 'DELETE',
  }),
  invalidatesTags: (result, error, id) => [
    { type: 'Users', id },
  ],
});

Эффект:

  • перезапрос только конкретного пользователя
  • отсутствие лишней сетевой нагрузки
  • точечное обновление состояния

Комбинированная стратегия тегирования

В реальных приложениях используется смешанный подход:

  • список получает тег LIST
  • каждый элемент получает свой id
  • мутации инвалидируют либо LIST, либо конкретный id
updateUser: builder.mutation({
  query: ({ id, ...patch }) => ({
    url: `/users/${id}`,
    method: 'PATCH',
    body: patch,
  }),
  invalidatesTags: (result, error, arg) => [
    { type: 'Users', id: arg.id },
    { type: 'Users', id: 'LIST' },
  ],
});

Такой подход гарантирует:

  • актуальность списка
  • актуальность карточки пользователя
  • минимизацию избыточных запросов

Поведение RTK Query при инвалидировании тегов

При срабатывании инвалидирования:

  1. RTK Query находит все cache-entry, связанные с тегом
  2. помечает их как «устаревшие»
  3. если компонент всё ещё использует данные — выполняет refetch
  4. обновляет кеш и подписчиков

Важно: инвалидирование не удаляет кеш мгновенно, оно инициирует перезапрос при необходимости.


Условное предоставление тегов

providesTags может возвращать пустой массив или undefined логически контролируя кэширование:

getPosts: builder.query({
  query: () => '/posts',
  providesTags: (result) =>
    result ? result.map(p => ({ type: 'Posts', id: p.id })) : [],
});

Если данные отсутствуют:

  • тегов нет
  • мутации не смогут триггерить перезапрос через этот endpoint

Ошибки в работе с тегами

1. Отсутствие tagTypes

Без регистрации тегов:

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

2. Слишком общие теги

providesTags: ['Users']

Проблема:

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

3. Отсутствие LIST-тега

Без него:

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

4. Несогласованность типов тегов

{ type: 'User' } // vs 'Users'

RTK Query считает их разными сущностями, даже если логически это одно и то же.


Оптимизация стратегии тегов

Эффективная система тегов строится вокруг нескольких принципов:

  • разделение коллекции и сущностей
  • использование LIST как агрегата
  • минимизация глобальной инвалидизации
  • привязка тегов к реальной структуре backend

Теги и нормализация данных

Хотя RTK Query не требует явной нормализации, теги часто работают как её упрощённая форма:

  • каждый id становится ключом сущности
  • LIST выступает индексом коллекции
  • инвалидирование заменяет ручное обновление store

Взаимодействие тегов с refetch-поведенем

RTK Query использует теги совместно с:

  • refetchOnMountOrArgChange
  • refetchOnFocus
  • refetchOnReconnect

Инвалидированный тег усиливает эти механизмы, заставляя систему переоценить актуальность данных.


Масштабирование системы тегов

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

  • разделять теги по доменам (Users, Auth, Posts)
  • избегать пересечений идентификаторов
  • строить предсказуемую карту зависимостей

Пример структуры:

tagTypes: ['Users', 'Posts', 'Auth', 'Notifications']

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


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

Система тегов фактически заменяет:

  • ручные dispatch действий
  • ручное обновление store
  • сложные цепочки useEffect для синхронизации данных

На уровне модели данных остаётся только логика:

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