Свойство invalidatesTags используется в mutation
endpoints для управления автоматической инвалидацией кэша. После
успешного выполнения мутации RTK Query помечает связанные данные как
устаревшие и инициирует повторное получение информации для всех query
endpoints, которые предоставляют соответствующие теги через
providesTags.
Механизм строится вокруг двух ключевых концепций:
providesTags — объявляет, какие данные предоставляет
query endpointinvalidatesTags — объявляет, какие данные становятся
неактуальными после mutation endpointБез invalidatesTags кэш после мутаций остаётся
неизменным. Это приводит к устаревшим данным в интерфейсе.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const postsApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api'
}),
tagTypes: ['Posts'],
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Posts']
}),
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: ['Posts']
})
})
})
Последовательность работы:
useGetPostsQuery()getPosts помечает данные тегом
PostscreatePostPostsprovidesTags: ['Posts']
автоматически перезапрашиваютсяГлавное преимущество invalidatesTags — автоматическая
синхронизация состояния.
Без инвалидации:
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
})
})
После создания записи список постов останется прежним до ручного обновления.
С инвалидацией:
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: ['Posts']
})
RTK Query автоматически обновит список.
invalidatesTagsinvalidatesTags может быть функцией.
Сигнатура:
invalidatesTags: (result, error, arg) => []
Аргументы:
| Аргумент | Описание |
|---|---|
result |
Ответ сервера |
error |
Ошибка запроса |
arg |
Аргумент мутации |
Часто инвалидация должна происходить только при успешной операции.
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, arg) =>
result
? ['Posts']
: []
})
Если сервер вернул ошибку, повторный запрос не выполняется.
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id }
]
})
Здесь инвалидируется только конкретный пост.
idRTK Query поддерживает объектный формат тегов:
{
type: 'Posts',
id: 15
}
Это позволяет обновлять отдельные сущности вместо полного списка.
providesTags и invalidatesTagsgetPost: builder.query({
query: (id) => `/posts/${id}`,
providesTags: (result, error, id) => [
{ type: 'Posts', id }
]
})
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id }
]
})
Полная инвалидация:
invalidatesTags: ['Posts']
Вызывает:
Точечная инвалидация:
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id }
]
Обновляет только нужные данные.
Распространённый паттерн:
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id },
'Posts'
]
Используется, когда:
Например:
deletePost: builder.mutation({
query: (id) => ({
url: `/posts/${id}`,
method: 'DELETE'
}),
invalidatesTags: (result, error, id) => [
{ type: 'Posts', id },
'Posts'
]
})
После удаления:
getPosts: builder.query({
query: () => '/posts',
providesTags: (result = []) => [
'Posts',
...result.map(post => ({
type: 'Posts',
id: post.id
}))
]
})
Каждый элемент списка получает собственный тег.
Например:
[
'Posts',
{ type: 'Posts', id: 1 },
{ type: 'Posts', id: 2 },
{ type: 'Posts', id: 3 }
]
Теперь можно:
RTK Query часто использует специальный идентификатор
LIST.
getPosts: builder.query({
query: () => '/posts',
providesTags: (result = []) => [
{ type: 'Posts', id: 'LIST' },
...result.map(post => ({
type: 'Posts',
id: post.id
}))
]
})
LISTLIST представляет весь список как отдельную
сущность.
Это позволяет:
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: [
{ type: 'Posts', id: 'LIST' }
]
})
Новая запись влияет именно на список, а не на отдельные сущности.
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id }
]
})
Список при этом не обновляется.
Удаление почти всегда влияет и на список, и на сущность.
deletePost: builder.mutation({
query: (id) => ({
url: `/posts/${id}`,
method: 'DELETE'
}),
invalidatesTags: (result, error, id) => [
{ type: 'Posts', id },
{ type: 'Posts', id: 'LIST' }
]
})
RTK Query поддерживает несколько tag types.
tagTypes: ['Posts', 'Users', 'Comments']
invalidatesTags: ['Posts', 'Comments']
Или:
invalidatesTags: [
{ type: 'Posts', id: 10 },
{ type: 'Comments', id: 55 }
]
Например, после создания комментария:
createComment: builder.mutation({
query: (body) => ({
url: '/comments',
method: 'POST',
body
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.postId },
{ type: 'Comments', id: 'LIST' }
]
})
Обновятся:
Инвалидация может зависеть от ответа сервера.
invalidatesTags: (result) => {
if (!result) {
return []
}
if (result.status === 'published') {
return ['Posts']
}
return []
}
invalidatesTags: (result) => [
{
type: 'Posts',
id: result.id
}
]
Это удобно, когда сервер генерирует идентификатор самостоятельно.
invalidatesTagstagTypescreateApi({
reducerPath: 'api',
baseQuery,
endpoints: () => ({})
})
Без:
tagTypes: ['Posts']
Теги работать не будут.
Ошибка:
providesTags: ['Post']
invalidatesTags: ['Posts']
Теги должны совпадать полностью.
providesTagsgetPosts: builder.query({
query: () => '/posts'
})
Если query endpoint не предоставляет теги, инвалидировать нечего.
Плохой вариант:
invalidatesTags: ['Posts']
для любой операции.
Это приводит к:
| Операция | Инвалидация |
|---|---|
| Создание | LIST |
| Обновление | конкретный id |
| Удаление | id + LIST |
| Массовое изменение | весь тип |
Даже при использовании updateQueryData инвалидация
остаётся полезной.
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData(
'getPost',
arg.id,
draft => {
draft.title = arg.title
}
)
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}
И дополнительно:
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id }
]
Оптимистичное обновление мгновенно меняет интерфейс, а инвалидация гарантирует синхронизацию с сервером.
Если endpoint использует polling:
useGetPostsQuery(undefined, {
pollingInterval: 5000
})
Инвалидация всё равно вызывает немедленный рефетч, независимо от таймера polling.
Даже если кэш ещё не удалён:
keepUnusedDataFor: 60
Инвалидация пометит данные устаревшими и обновит активные подписки.
RTK Query обновляет только активные подписки.
Если query endpoint сейчас не используется компонентами:
При следующем использовании endpoint данные загрузятся заново.
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const postsApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({
baseUrl: '/api'
}),
tagTypes: ['Posts'],
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts',
providesTags: (result = []) => [
{ type: 'Posts', id: 'LIST' },
...result.map(post => ({
type: 'Posts',
id: post.id
}))
]
}),
getPost: builder.query({
query: (id) => `/posts/${id}`,
providesTags: (result, error, id) => [
{ type: 'Posts', id }
]
}),
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: [
{ type: 'Posts', id: 'LIST' }
]
}),
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Posts', id: arg.id }
]
}),
deletePost: builder.mutation({
query: (id) => ({
url: `/posts/${id}`,
method: 'DELETE'
}),
invalidatesTags: (result, error, id) => [
{ type: 'Posts', id },
{ type: 'Posts', id: 'LIST' }
]
})
})
})