Система тегов в RTK Query является основой для автоматической инвалидации и обновления данных в кэше. Она позволяет декларативно описывать связи между запросами и мутациями без ручного управления рефетчем.
Теги представляют собой строковые или объектные идентификаторы,
которые ассоциируются с конкретными наборами данных. При изменении
данных через мутацию RTK Query использует механизм
invalidatesTags, чтобы определить, какие запросы необходимо
обновить.
Ключевая идея заключается в разделении ответственности:
Поле providesTags используется внутри query-эндпоинтов.
Оно определяет, какие теги ассоциируются с результатом запроса.
Каждый запрос может:
Базовая форма:
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Posts']
})
В этом случае весь результат запроса связан с тегом
Posts. Любая мутация, инвалидирующая этот тег, приведёт к
повторному запросу.
Часто необходимо привязать теги не только к списку, но и к отдельным сущностям. Например, каждый пост может иметь свой собственный тег.
getPosts: builder.query({
query: () => '/posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Post', id })),
{ type: 'Post', id: 'LIST' }
]
: [{ type: 'Post', id: 'LIST' }]
})
Здесь реализуется два уровня тегирования:
Это позволяет:
Поле invalidatesTags используется в мутациях. Оно
указывает, какие теги должны считаться устаревшими после выполнения
операции.
Простейший пример:
addPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: ['Post']
})
После успешного добавления нового поста все запросы, связанные с
тегом Post, будут автоматически перезапрошены.
Механизм работает через сопоставление типов и идентификаторов:
providesTags помечает данные как актуальные;invalidatesTags помечает их как устаревшие;Пример связки:
getPost: builder.query({
query: (id) => `/posts/${id}`,
providesTags: (result, error, id) => [{ type: 'Post', id }]
})
updatePost: builder.mutation({
query: ({ id, ...patch }) => ({
url: `/posts/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }]
})
В этом случае обновление конкретного поста приводит к рефетчу только этого поста, а не всего списка.
Одной из распространённых практик является использование специального
идентификатора LIST. Он представляет весь набор данных.
providesTags: [
{ type: 'Post', id: 'LIST' }
]
Мутации могут инвалидировать весь список:
invalidatesTags: [
{ type: 'Post', id: 'LIST' }
]
Это удобно в случаях:
RTK Query поддерживает точечное обновление. Это особенно важно для производительности.
Пример:
updateComment: builder.mutation({
query: ({ postId, commentId, ...patch }) => ({
url: `/posts/${postId}/comments/${commentId}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, { postId, commentId }) => [
{ type: 'Comment', id: commentId }
]
})
Здесь обновляется только конкретный комментарий, без затрагивания остальных данных.
В реальных приложениях данные часто связаны между собой. Один запрос может предоставлять несколько типов тегов.
getPostWithComments: builder.query({
query: (id) => `/posts/${id}?include=comments`,
providesTags: (result, error, id) => [
{ type: 'Post', id },
{ type: 'Comment', id: 'LIST' }
]
})
Такая модель позволяет:
Иногда данные могут отсутствовать или быть ошибочными. В таких
случаях providesTags должен учитывать состояние
результата.
providesTags: (result, error, id) =>
error
? []
: result
? [{ type: 'Post', id }]
: []
Это предотвращает некорректную привязку кэша к несуществующим данным.
Важно учитывать, что:
providesTags может не
сработать;Пример поведения:
getUser: builder.query({
query: (id) => `/users/${id}`,
providesTags: (result, error, id) =>
error ? [] : [{ type: 'User', id }]
})
Если запрос падает, тег не закрепляется, и система не будет пытаться инвалидировать несуществующий кэш.
При обновлении одной сущности может потребоваться обновление другой:
updateOrder: builder.mutation({
query: (order) => ({
url: `/orders/${order.id}`,
method: 'PUT',
body: order
}),
invalidatesTags: (result, error, order) => [
{ type: 'Order', id: order.id },
{ type: 'User', id: order.userId }
]
})
Это полезно в системах, где данные взаимосвязаны.
Иногда требуется сбросить весь кэш определённого типа:
logout: builder.mutation({
queryFn: () => ({ data: true }),
invalidatesTags: ['Post', 'User', 'Comment']
})
Такой подход часто используется при смене пользователя или выходе из системы.
В крупных приложениях важно избегать случайных ошибок в строковых тегах. Обычно применяются константы:
const TAGS = {
POST: 'Post',
COMMENT: 'Comment',
USER: 'User'
}
И использование:
providesTags: [{ type: TAGS.POST, id: 'LIST' }]
Это снижает риск рассинхронизации между query и mutation слоями.
Чем более точечные теги используются, тем:
Однако чрезмерная детализация может привести к:
Баланс достигается через комбинацию:
Одной из распространённых ошибок является отсутствие согласованности
между providesTags и invalidatesTags.
Например:
Post;Posts.В таком случае совпадение не произойдёт, и рефетч не запустится.
Ещё одна ошибка — отсутствие id при динамическом
тегировании, что приводит к невозможности точечной инвалидации.
При проектировании системы тегов обычно используется следующая логика:
Такая структура делает кэш предсказуемым и управляемым без ручного вмешательства в refetch logic.