RTK Query оперирует данными не как набором изолированных запросов, а как системой взаимосвязанных сущностей, где каждый endpoint участвует в формировании общего кэша. Внутри этой модели ключевую роль играет идея связей между объектами данных, их идентификации и синхронизации состояния между различными частями приложения.
Основой работы с связанными сущностями является нормализация данных на уровне клиентского кэша. Хотя RTK Query не требует явной нормализации, логика построения API часто опирается на стабильные идентификаторы.
Типичная сущность должна иметь:
id)Пример структуры:
{
id: 42,
title: "Post title",
authorId: 7
}
Даже без явного entity adapter подхода, RTK Query использует
query cache key + endpoint + аргументы как основу хранения,
что позволяет моделировать связи через повторное использование данных и
тегирование.
Связанные сущности в RTK Query реализуются через систему тегов:
providesTagsinvalidatesTagsЭта система превращает endpoint в декларацию зависимостей между сущностями.
getPosts: builder.query({
query: () => '/posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Post', id })),
{ type: 'Post', id: 'LIST' }
]
: [{ type: 'Post', id: 'LIST' }]
})
Каждый элемент списка становится отдельной сущностью в системе тегов. Это позволяет обновлять как весь список, так и отдельный объект.
Связанные сущности начинают проявлять себя при изменениях данных. При мутации:
addPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: [{ type: 'Post', id: 'LIST' }]
})
обновление списка автоматически приводит к перезапросу всех зависимых query, которые предоставляют этот тег.
Это формирует каскад:
Таким образом достигается слабосвязанная, но реактивная модель данных.
Частый сценарий — связь пользователя и его сущностей:
getUserPosts: builder.query({
query: (userId) => `/users/${userId}/posts`,
providesTags: (result, error, userId) =>
result
? [
{ type: 'UserPosts', id: userId },
...result.map(post => ({ type: 'Post', id: post.id }))
]
: [{ type: 'UserPosts', id: userId }]
})
Здесь одновременно существуют два уровня сущностей:
UserPosts)Post)Такой подход позволяет обновлять либо весь набор, либо отдельные элементы без потери консистентности.
Аргументы endpoint становятся частью ключа кэша, что позволяет моделировать отношения без явной нормализации.
getCommentsByPost: builder.query({
query: (postId) => `/posts/${postId}/comments`,
providesTags: (result, error, postId) =>
result
? [
{ type: 'Comments', id: postId }
]
: []
})
Каждый postId формирует независимую сущность в кэше. Это
создаёт изолированные графы данных внутри одного API slice.
При усложнении API формируется граф:
Связи выражаются не напрямую, а через пересечение тегов.
Пример:
{Post, id}{Comments, postId}{User, id}При изменении пользователя:
invalidatesTags: [{ type: 'User', id }]
могут автоматически обновляться:
Это достигается не явными связями, а перекрытием тегов.
Связанные сущности могут формироваться на уровне селектора:
const { data: post } = useGetPostQuery(id, {
selectFromResult: ({ data }) => ({
data,
authorId: data?.authorId
})
})
Это позволяет извлекать часть сущности и использовать её для запуска зависимых запросов:
const { data: author } = useGetUserQuery(authorId, {
skip: !authorId
})
Так формируется цепочка зависимых сущностей:
Связанные сущности в RTK Query проявляются особенно явно при композиции компонентов:
Каждый уровень может использовать собственный cache key, но при этом ссылаться на один и тот же источник данных.
Пример:
getPostsgetPostgetCommentsByPostНесмотря на разные endpoints, теговая система синхронизирует их состояние.
При наличии связанных сущностей возникает проблема устаревших связей. RTK Query решает её через:
Если комментарий изменился:
invalidatesTags: (result, error, arg) => [
{ type: 'Comments', id: arg.postId },
{ type: 'Post', id: arg.postId }
]
обновляются сразу две сущности:
Хотя RTK Query не является полноценным ORM, теги позволяют имитировать поведение реляционной модели:
idpostId, userIdprovidesTags / invalidatesTagsЭто создаёт гибридную модель между REST-кэшированием и нормализованным хранилищем.
При повторном обращении к одной сущности RTK Query предотвращает лишние запросы:
Это особенно важно для связанных сущностей, где один
userId может использоваться в десятках компонентов
одновременно.
Связи между сущностями обычно развиваются постепенно:
providesTagsinvalidatesTagsНа этом этапе API начинает вести себя как реактивная система данных, а не набор запросов.
При росте приложения ключевыми становятся:
Нарушение этих правил приводит к разрыву связей между сущностями и неконтролируемым устареванием кэша.