Кэш RTK Query строится вокруг endpoint + аргументы запроса. Каждый вызов хука формирует уникальный ключ, по которому сохраняются данные:
getPosts() → один кэш-ключgetPosts({ page: 1 }) → другой кэш-ключgetPosts({ page: 2 }) → третий кэш-ключКлюч формируется через сериализацию аргументов, поэтому даже логически одинаковые, но структурно разные объекты могут создавать разные записи в кэше.
Важно учитывать:
transformResponse + entity adapter)RTK Query предоставляет несколько низкоуровневых инструментов для прямого вмешательства в кэш:
api.util.updateQueryDataapi.util.invalidateTagsapi.util.upsertQueryDataapi.util.resetApiStateКаждый из них решает разные задачи: от точечного изменения записи до полного сброса состояния API-слайса.
updateQueryData позволяет синхронно изменить данные в
кэше конкретного запроса без повторного запроса на сервер.
Сигнатура:
dispatch(
api.util.updateQueryData(endpointName, args, (draft) => {
// мутирование draft (Immer)
})
);
Принцип работы:
endpointName + argsПример обновления списка:
dispatch(
api.util.updateQueryData('getPosts', undefined, (draft) => {
const post = draft.find(p => p.id === 1);
if (post) {
post.title = 'Обновлённый заголовок';
}
})
);
Особенность:
При работе с параметризованными запросами важно точно передавать аргументы:
dispatch(
api.util.updateQueryData('getPosts', { page: 2 }, (draft) => {
draft.items.push({
id: 999,
title: 'Новый пост'
});
})
);
Если аргументы не совпадают с кэшем, обновление не произойдёт.
upsertQueryData используется для полной установки
значения кэша.
dispatch(
api.util.upsertQueryData('getPost', 1, {
id: 1,
title: 'Созданный или заменённый пост'
})
);
Поведение:
Это полезно при:
Один из ключевых сценариев ручного обновления кэша — оптимистические изменения.
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPosts', undefined, (draft) => {
draft.unshift({
id: Date.now(),
...arg
});
})
);
try {
await queryFulfilled;
} catch {
patchResult.undo();
}
}
});
Ключевые моменты:
undo()updateQueryData возвращает объект patch, содержащий
метод отката:
const patchResult = dispatch(
api.util.updateQueryData('getPosts', undefined, draft => {
draft.push(newPost);
})
);
// откат
patchResult.undo();
Это позволяет строить безопасные оптимистические сценарии без ручного хранения предыдущего состояния.
Хотя это не прямое редактирование кэша, invalidateTags
часто используется совместно с ручным управлением:
dispatch(
api.util.invalidateTags(['Posts'])
);
Поведение:
Распространённый гибридный подход:
dispatch(
api.util.updateQueryData('getPosts', undefined, draft => {
draft.unshift(tempPost);
})
);
dispatch(
api.util.invalidateTags(['Posts']);
);
Логика:
Для полного сброса всех запросов:
dispatch(api.util.resetApiState());
Применяется в случаях:
Поведение:
При сложных интерфейсах часто требуется синхронное обновление нескольких записей:
dispatch(
api.util.updateQueryData('getPost', 1, draft => {
draft.title = 'Обновлено';
})
);
dispatch(
api.util.updateQueryData('getPosts', undefined, draft => {
const item = draft.find(p => p.id === 1);
if (item) item.title = 'Обновлено';
})
);
Проблема:
Решение:
RTK Query использует Immer, поэтому внутри
updateQueryData допускается “мутационный” стиль:
draft.title = 'Новое значение';
draft.items.push(newItem);
Фактически:
Несовпадение аргументов запроса
updateQueryData('getPosts', { page: '1' }, ...)
и
getPosts({ page: 1 })
Это разные ключи.
Попытка обновить несуществующий кэш
Игнорирование нескольких подписчиков
Ручное обновление кэша часто используется для уменьшения сетевой нагрузки:
RTK Query в этом сценарии выступает как гибрид: