WebMriya
Назад до статей

Інвалідація кешу в React Query: не перезавантажуйте все

Практичний розбір інвалідації кешу в React Query: як оновлювати потрібні дані, уникати застарілих екранів і зайвих запитів.

React
TypeScript
Frontend Architecture
Software Architecture
W
Frontend Architecture & Legacy Modernization
8 жовтня 2026 р. · 9 хв читання

Користувач редагує профіль, повертається на дашборд і бачить старе ім’я. Команда додає глобальний refetch після кожної мутації. Екран стає коректним, але тепер кожна дрібна зміна запускає запити для сповіщень, дозволів, звітів і сторонніх списків. Мережевий трафік зростає, індикатори завантаження блимають усюди, а ніхто не може пояснити, які саме дані справді застаріли.

Зазвичай це не проблема рендерингу React. Це проблема володіння кешем і його залежностями. Бібліотека для роботи із серверним станом, наприклад TanStack Query, може підтримувати дані актуальними, але лише якщо застосунок точно моделює ідентичність запитів і наслідки мутацій. Інвалідувати все без розбору безпечно приблизно так само, як перезапускати всі сервіси після однієї зміни конфігурації: це працює, але приховує слабку модель залежностей.

Інвалідація кешу — це проблема залежностей

Кожен запит має ключ, який описує серверні дані, що він представляє. Запит для конкретного проєкту, сторінки задач або дозволів користувача не повинен бути анонімним. Його ключ — це контракт між інтерфейсом і кешем.

ts
const projectQuery = useQuery({
  queryKey: ["project", projectId],
  queryFn: () => api.projects.get(projectId),
});

const tasksQuery = useQuery({
  queryKey: ["tasks", { projectId, page, filter }],
  queryFn: () => api.tasks.list({ projectId, page, filter }),
});

Перший ключ показує, що результат належить конкретному проєкту. Другий додає до ідентичності проєкт, сторінку та фільтр. Якщо мутація змінює назву проєкту, запит проєкту безпосередньо зачеплено. Відфільтрована сторінка задач, найімовірніше, ні. Якщо змінюється статус задачі, можуть бути зачеплені кілька списків задач, але метадані проєкту — ні.

Використовуйте стабільні фабрики ключів

Розкидані по компонентах масиви рядків легко вводять в оману. Один компонент може використовувати ["tasks", projectId], а інший — ["project-tasks", projectId]. Обидва варіанти виглядають логічно, але інвалідація одного не зачепить інший. Фабрики ключів роблять словник кешу явним і синхронізують читання з інвалідацією.

ts
export const taskKeys = {
  all: ["tasks"] as const,
  lists: () => [...taskKeys.all, "list"] as const,
  list: (params: TaskListParams) => [...taskKeys.lists(), params] as const,
  detail: (taskId: string) => [...taskKeys.all, "detail", taskId] as const,
};

Така ієрархія корисна, бо TanStack Query може зіставляти префікси. Інвалідація taskKeys.all зачепить усі запити задач. Інвалідація taskKeys.lists() зачепить списки, але залишить detail-запити без змін. Конкретний ключ згенерованого списку зачепить лише одну комбінацію параметрів.

Обирайте найменшу коректну область інвалідації

Правильний масштаб інвалідації залежить від того, які дані може змінити мутація на сервері. Не обирайте широкий ключ лише через зручність; обирайте його, коли наслідки мутації справді цього вимагають.

МутаціяЗазвичай зачіпаєЗазвичай не зачіпає
Перейменування проєктуДеталі проєкту та списки проєктівСторінки задач, сповіщення
Зміна статусу задачіДеталі задачі, відповідні списки, лічильникиОпис проєкту
Оновлення аватара поточного користувачаПрофіль користувача та видимі короткі дані про користувачівНепов’язані дані проєктів

Для перейменування проєкту інвалідуйте деталі проєкту та префікси списків проєктів. Для зміни статусу задачі інвалідуйте списки задач і агрегатні запити, що показують лічильники. Це складніше, ніж один раз викликати invalidateQueries() на рівні всього застосунку, зате завантаження стає передбачуваним, а зайвих запитів менше.

Інвалідувати чи оновлювати напряму

Інвалідація позначає відповідні запити як застарілі й дозволяє активним запитам виконати refetch. Це найбезпечніший варіант за замовчуванням, коли сервер застосовує бізнес-правила, обчислює поля або повертає дані, які складно відтворити локально. Пряме оновлення кешу доречне, коли відповідь мутації є авторитетною і має ту саму форму, що й кешована сутність.

ts
const updateTask = useMutation({
  mutationFn: api.tasks.update,
  onSuccess: (updatedTask) => {
    queryClient.setQueryData(
      taskKeys.detail(updatedTask.id),
      updatedTask,
    );
    queryClient.invalidateQueries({ queryKey: taskKeys.lists() });
  },
});

Тут кеш деталей оновлюється одразу з відповіді, а списки інвалідуються, бо могли змінитися сортування, фільтрація, пагінація або похідні мітки. Такий гібридний підхід часто кращий, ніж вибір між «вручну оновити все» і «перезавантажити все».

Будьте уважні зі списками

Саме зі списками інвалідація кешу стає нетривіальною. Одна сутність може бути присутньою в кількох списках: усі задачі, призначені задачі, прострочені задачі та результат пошуку з пагінацією. Оновлення одного detail-запиту автоматично не перемістить сутність між цими списками. Якщо мутація змінює поле, за яким виконується фільтрація або сортування, краще інвалідувати потрібне сімейство списків, а не намагатися виправити кожну можливу сторінку.

Якщо результатом є просте додавання до відомого списку, setQueryData може бути ефективним. Якщо операція впливає на порядок, дозволи, підсумки або серверну фільтрацію, цільовий refetch зазвичай надійніший. Оновлення кешу, яке виглядає правильним для першої сторінки, може непомітно створити помилкові дані на третій.

Уникайте перегонів між інвалідаціями

Дві мутації можуть завершитися в іншому порядку, ніж їх запустив користувач. Якщо кожен success-обробник записує частину даних у кеш, стара відповідь може перезаписати новішу. Це особливо часто трапляється в автозбереженні, редагованих таблицях і швидких змінах статусу.

  1. Передавайте версію, значення updatedAt або ідентифікатор мутації, якщо API підтримує виявлення конфліктів.
  2. Скасовуйте або послідовно виконуйте мутації, що перетинаються, якщо операцію небезпечно виконувати паралельно.
  3. Використовуйте відповідь сервера як джерело істини, а не об’єднуйте її із застарілими локальними полями.
  4. Після складної або конфліктної мутації повторно завантажуйте зачеплений запит.

Той самий принцип стосується оптимістичних оновлень. Вони роблять інтерфейс швидшим на відчуття, але потребують відкату й узгодження з сервером. Докладніше про помилки запитів і мутації, що перетинаються, читайте в матеріалі Оптимістичні оновлення в React без пошкодження даних.

Практичний чекліст для рев’ю

  • Чи можете ви назвати точні ключі запитів, на які може вплинути мутація?
  • Чи генеруються ключі з одного спільного словника?
  • Чи містить відповідь мутації достатньо даних для безпечного прямого оновлення кешу?
  • Чи могли змінитися фільтрація, сортування, пагінація, дозволи або агрегатні лічильники?
  • Чи можуть два запити завершитися в іншому порядку?
  • Чи не інвалідуються без потреби неактивні запити?
  • Чи перевіряють тести поведінку кешу, а не лише факт виконання мутації?

На цій межі важлива і runtime-валідація. Типізована відповідь мутації не доводить, що сервер повернув форму, яку очікує кеш. Перевіряйте зовнішні дані перед записом у клієнтський кеш; логіка тут така сама, як і для будь-якої іншої межі API. Дивіться Runtime-валідація TypeScript: закрийте розрив на межах API.

Корисне правило

Інвалідуйте за наслідком у домені, а не за розташуванням компонента. Мутація повинна описувати, які серверні поняття вона може змінити, а ключі запитів — робити ці поняття адресними. Коли модель залежностей явна, оновлення кешу стає невеликим проєктним рішенням, а не глобальним аварійним механізмом.

Такий підхід до стану й потоків даних особливо важливий у складних застосунках із великою кількістю екранів, дозволів і паралельних взаємодій. Якщо наявний фронтенд виріс навколо широких refetch-ів і непередбачуваних застарілих даних, розробка складних вебзастосунків WebMriya допоможе повернути ці межі під контроль.

Поширені запитання

Чи потрібно після кожної мутації інвалідувати всі запити?

Ні. Інвалідуйте найменшу область ключів, яка охоплює реальні серверні наслідки мутації. Широка інвалідація може бути тимчасовим заходом безпеки, але не повинна залишатися постійною моделлю роботи з даними.

Коли використовувати setQueryData замість інвалідації?

Використовуйте setQueryData, коли відповідь мутації є авторитетною і відповідає формі кешованої сутності. Інвалідація краща, якщо могли змінитися серверне сортування, фільтрація, дозволи, агрегати або бізнес-правила.

Навіщо потрібні фабрики ключів запитів?

Вони створюють єдиний словник для читання та інвалідації кешованих даних. Також вони роблять ієрархічну інвалідацію передбачуваною: можна звернутися до всіх списків, одного сімейства списків або конкретної сутності без копіювання рядкових літералів.