В RTK Query система кэширования строится вокруг ключей запросов и
тегов, которые позволяют связывать полученные данные с последующей
инвалидацией. В query endpoints параметр providesTags
определяет, какие логические теги “помечают” результат запроса после его
выполнения. Эти теги затем используются мутациями для точечного
обновления кэша без необходимости вручную управлять состоянием.
Каждый query endpoint может “предоставлять” один или несколько тегов. Эти теги описывают, какие сущности представлены в ответе.
Основная цель:
Перед использованием providesTags необходимо объявить
типы тегов на уровне API:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Post', 'User'],
endpoints: (builder) => ({
// endpoints here
}),
});
tagTypes задаёт список допустимых категорий тегов. Любой
тег вне этого списка не будет участвовать в системе инвалидации.
Самый прямой вариант — вернуть фиксированный набор тегов:
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Post'],
});
Такой подход означает: результат запроса относится ко всем сущностям
типа Post.
Недостаток этого подхода заключается в отсутствии гранулярности.
Любая мутация с тегом Post приведёт к рефетчу всего
списка.
Более гибкий вариант — возвращать массив объектов:
getPosts: builder.query({
query: () => '/posts',
providesTags: [
{ type: 'Post', id: 'LIST' }
],
});
Здесь появляется концепция виртуального идентификатора
LIST, который обычно используется для списков.
Такой подход позволяет разделять:
LIST)id)Наиболее распространённый вариант — генерация тегов на основе ответа API:
getPosts: builder.query({
query: () => '/posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Post', id })),
{ type: 'Post', id: 'LIST' },
]
: [{ type: 'Post', id: 'LIST' }],
});
Здесь происходит следующее:
{ type: 'Post', id }{ id: 'LIST' }Функция providesTags получает не только
result, но также может учитывать состояние:
providesTags: (result, error, arg) => {
if (error) return [];
if (!result) return [{ type: 'Post', id: 'LIST' }];
return [
...result.map((item) => ({ type: 'Post', id: item.id })),
{ type: 'Post', id: 'LIST' },
];
}
Такой вариант предотвращает загрязнение кэша при ошибках запроса.
Третий параметр arg позволяет учитывать параметры
запроса:
getPostsByUser: builder.query({
query: (userId) => `/users/${userId}/posts`,
providesTags: (result, error, userId) =>
result
? [
...result.map((post) => ({
type: 'Post',
id: post.id,
})),
{ type: 'Post', id: `USER_${userId}` },
]
: [{ type: 'Post', id: `USER_${userId}` }],
});
В этом случае кэш становится сегментированным по пользователям.
providesTags сам по себе не вызывает обновление данных.
Он лишь регистрирует принадлежность результата к тегам.
Обновление происходит через мутации:
addPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body,
}),
invalidatesTags: [{ type: 'Post', id: 'LIST' }],
});
После успешной мутации все query endpoints, предоставляющие тег
{ type: 'Post', id: 'LIST' }, автоматически
перезапрашиваются.
Если query возвращает посты с индивидуальными тегами:
providesTags: (result) =>
result
? result.map((post) => ({ type: 'Post', id: post.id }))
: [],
Тогда мутация может инвалидировать только один элемент:
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch,
}),
invalidatesTags: (result, error, { id }) => [
{ type: 'Post', id },
],
});
Это позволяет избежать полного рефетча списка.
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Post', id })),
{ type: 'Post', id: 'LIST' },
]
: [{ type: 'Post', id: 'LIST' }];
Используется для коллекций, где важны как отдельные элементы, так и весь список.
providesTags: [{ type: 'Post', id: 'LIST' }];
Подходит для статичных или редко изменяемых списков.
providesTags: (result, error, category) => [
{ type: 'Post', id: `CATEGORY_${category}` },
];
Используется при фильтрации данных по категориям.
RTK Query хранит результаты запросов в нормализованном виде по ключу endpoint + аргументы. Теги добавляют дополнительный уровень связи:
Избыточное количество тегов приводит к росту памяти и усложнению инвалидации. Обычно используется баланс:
Распространённые проблемы:
LIST тега, что усложняет обновление
списковprovidesTags и
invalidatesTagsПри срабатывании инвалидации RTK Query:
Один endpoint может возвращать разные типы тегов одновременно:
providesTags: (result, error, arg) => {
const baseTags = [{ type: 'Post', id: 'LIST' }];
const userTags = result
? result.map((p) => ({ type: 'User', id: p.userId }))
: [];
return [...baseTags, ...userTags];
};
Такой подход используется, когда один endpoint влияет на несколько сущностей.
Механизм можно представить как трёхуровневую систему:
Эта модель позволяет строить предсказуемую и масштабируемую систему синхронизации клиентского состояния с сервером без ручного управления кэшем.