Система тегов в RTK Query является центральным механизмом декларативного управления инвалидированием кэша. Она устраняет необходимость вручную отслеживать зависимости между запросами и мутациями, заменяя императивную логику обновления данных на описательную модель «что связано с чем».
В основе лежит идея: каждый запрос может «помечать» свои данные тегами, а мутации — «сбрасывать» эти теги, автоматически инициируя повторные запросы там, где это необходимо.
Тег в RTK Query — это метка, которая ассоциируется с данными, полученными через endpoint.
Система работает по двум основным направлениям:
providesTags — какие теги «выдаются» запросомinvalidatesTags — какие теги «сбрасываются»
мутациейКогда тег инвалидируется, RTK Query автоматически понимает, какие запросы нужно перезапросить.
Перед использованием тегов их необходимо зарегистрировать в 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, он не будет корректно
участвовать в системе кэш-инвалидации.
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' }],
});
Использование { id: 'LIST' } позволяет:
Мутации используют 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 },
],
});
Эффект:
В реальных приложениях используется смешанный подход:
LISTidLIST, либо конкретный
idupdateUser: 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' },
],
});
Такой подход гарантирует:
При срабатывании инвалидирования:
Важно: инвалидирование не удаляет кеш мгновенно, оно инициирует перезапрос при необходимости.
providesTags может возвращать пустой массив или
undefined логически контролируя кэширование:
getPosts: builder.query({
query: () => '/posts',
providesTags: (result) =>
result ? result.map(p => ({ type: 'Posts', id: p.id })) : [],
});
Если данные отсутствуют:
Без регистрации тегов:
providesTags: ['Users']
Проблема:
Без него:
{ type: 'User' } // vs 'Users'
RTK Query считает их разными сущностями, даже если логически это одно и то же.
Эффективная система тегов строится вокруг нескольких принципов:
LIST как агрегатаХотя RTK Query не требует явной нормализации, теги часто работают как её упрощённая форма:
id становится ключом сущностиLIST выступает индексом коллекцииRTK Query использует теги совместно с:
refetchOnMountOrArgChangerefetchOnFocusrefetchOnReconnectИнвалидированный тег усиливает эти механизмы, заставляя систему переоценить актуальность данных.
При росте приложения появляется необходимость:
Пример структуры:
tagTypes: ['Users', 'Posts', 'Auth', 'Notifications']
Каждый домен изолирован, что уменьшает риск каскадных перезапросов.
Система тегов фактически заменяет:
На уровне модели данных остаётся только логика: