Мутации в TanStack Query представляют собой операции изменения
серверного состояния: создание, обновление или удаление данных. В
отличие от запросов (queries), которые описывают чтение
данных, мутации изменяют источник истины, и поэтому их поведение требует
более строгого контроля, особенно в условиях асинхронности, гонок
запросов и пользовательских интеракций.
Отмена мутаций — это механизм управления жизненным циклом мутации, позволяющий прерывать выполнение операции до её завершения или игнорировать её результат, если он больше не актуален.
Мутация в TanStack Query проходит несколько этапов:
mutate)mutationFnОтмена может происходить на разных уровнях:
mutationFnmutationFn (если поддерживается
AbortController)TanStack Query использует стандартный веб API
AbortController для отмены асинхронных операций.
import { useMutation } from '@tanstack/react-query'
const mutation = useMutation({
mutationFn: async ({ id, signal }) => {
const res = await fetch(`/api/items/${id}`, {
method: 'DELETE',
signal,
})
if (!res.ok) {
throw new Error('Ошибка удаления')
}
return res.json()
},
})
В этом примере signal автоматически передаётся TanStack
Query в mutationFn. Если мутация отменяется,
signal.abort() прерывает fetch.
TanStack Query предоставляет методы управления активной мутацией через объект мутации.
const mutation = useMutation({
mutationFn: async (data) => {
const controller = new AbortController()
const res = await fetch('/api/items', {
method: 'POST',
body: JSON.stringify(data),
signal: controller.signal,
})
return res.json()
},
})
mutation.reset()
Однако reset() не отменяет сетевой запрос. Для реальной
отмены требуется хранить AbortController
самостоятельно.
Наиболее гибкий подход — явное управление контроллером:
let controller
const mutation = useMutation({
mutationFn: async (data) => {
controller = new AbortController()
const res = await fetch('/api/items', {
method: 'POST',
body: JSON.stringify(data),
signal: controller.signal,
})
return res.json()
},
})
const cancelMutation = () => {
controller?.abort()
}
Этот подход позволяет полностью контролировать жизненный цикл запроса, включая отмену до завершения.
onMutate используется для оптимистических обновлений и
подготовки состояния перед мутацией. Он также может возвращать контекст,
который помогает откатывать изменения.
const mutation = useMutation({
mutationFn: async (newItem) => {
return fetch('/api/items', {
method: 'POST',
body: JSON.stringify(newItem),
}).then(res => res.json())
},
onMutate: async (newItem) => {
await queryClient.cancelQueries({ queryKey: ['items'] })
const previous = queryClient.getQueryData(['items'])
queryClient.setQueryData(['items'], old => [
...old,
{ ...newItem, temp: true },
])
return { previous }
},
onError: (err, newItem, context) => {
queryClient.setQueryData(['items'], context.previous)
},
})
Хотя это не «отмена» сетевого запроса напрямую, такой механизм позволяет отменить эффект мутации на уровне состояния.
Даже если запрос не был физически прерван, его результат можно игнорировать.
let isCancelled = false
const mutation = useMutation({
mutationFn: async (data) => {
const res = await fetch('/api/items', {
method: 'POST',
body: JSON.stringify(data),
})
const result = await res.json()
if (isCancelled) {
return
}
return result
},
})
const cancel = () => {
isCancelled = true
}
Этот подход полезен, когда API или окружение не поддерживает
AbortController.
В React-окружении частая задача — предотвращение обновления состояния после размонтирования компонента.
TanStack Query частично решает это автоматически, но при ручных мутациях требуется учитывать жизненный цикл:
useEffect(() => {
return () => {
controller?.abort()
}
}, [])
Это предотвращает попытки обновления состояния после ухода компонента из дерева.
При быстрых последовательных вызовах мутаций может возникать ситуация гонки:
Решения:
const mutation = useMutation({
mutationFn: async (data) => {
await queue.add(() => api.save(data))
},
})
let lastCallId = 0
const mutation = useMutation({
mutationFn: async (data) => {
const callId = ++lastCallId
const result = await api.save(data)
if (callId !== lastCallId) return
return result
},
})
Мутации часто сопровождаются invalidateQueries. Отмена
влияет на это поведение косвенно.
Если мутация отменена:
onSuccess не вызываетсяinvalidateQueries не выполняетсяconst mutation = useMutation({
mutationFn: api.updateItem,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['items'] })
},
})
При отмене выполнения onSuccess логически не
достигается.
При использовании Axios или других библиотек требуется адаптация.
import axios from 'axios'
const mutation = useMutation({
mutationFn: async ({ id, signal }) => {
const res = await axios.delete(`/api/items/${id}`, {
signal,
})
return res.data
},
})
Axios начиная с современных версий поддерживает
AbortController, что упрощает интеграцию.
Без хранения контроллера невозможно прервать запрос после запуска.
AbortController не влияет на уже завершённый промис, поэтому результат может прийти позже и быть неактуальным.
Отмена запроса не отменяет уже выполненные side effects, если они не защищены проверками.
const mutation = useMutation({
mutationFn: async (data) => {
const controller = new AbortController()
const promise = fetch('/api/items', {
method: 'POST',
body: JSON.stringify(data),
signal: controller.signal,
})
promise.abortController = controller
const res = await promise
return res.json()
},
})
const cancel = () => {
mutation?.mutationFn?.abortController?.abort()
}
Хотя TanStack Query не требует такого паттерна, он полезен при сложных сценариях.
Отмена мутаций в TanStack Query — это не единый механизм, а комбинация нескольких уровней:
AbortControlleronMutate и откатПравильная архитектура всегда опирается на сочетание этих подходов, а не на один инструмент.