Инспекция мутаций

Инспекция мутаций в TanStack Query представляет собой набор инструментов и API для наблюдения за жизненным циклом операций изменения данных. В отличие от запросов (queries), которые ориентированы на чтение и кеширование серверного состояния, мутации (mutations) отвечают за создание, изменение и удаление данных.

Инспекция мутаций необходима для:

  • анализа текущего состояния операций;
  • отслеживания ошибок;
  • отображения глобальных индикаторов загрузки;
  • построения инструментов мониторинга;
  • отладки оптимистичных обновлений;
  • синхронизации UI между компонентами;
  • диагностики конкурентных операций.

Внутри TanStack Query мутации хранятся в отдельном кеше — MutationCache.


Архитектура MutationCache

Каждая мутация после запуска регистрируется внутри MutationCache.

Структурно кеш мутаций отличается от кеша запросов:

QueryCache MutationCache
Хранит серверные данные Хранит операции изменения
Ориентирован на переиспользование Ориентирован на жизненный цикл
Использует queryKey Использует mutationKey
Долговременное хранение Кратковременное хранение
Может быть stale Обычно одноразовый процесс

Создание QueryClient автоматически включает MutationCache.

import { QueryClient } from '@tanstack/react-query'

const queryClient = new QueryClient()

Внутри:

queryClient.getMutationCache()

возвращает экземпляр MutationCache.


Жизненный цикл мутации

Каждая мутация проходит несколько этапов:

  1. idle
  2. pending
  3. success
  4. error

Типичный поток:

idle
  ↓
pending
  ↓
success | error

Инспекция позволяет получать доступ к каждому состоянию в реальном времени.


useMutationState

Хук useMutationState предоставляет глобальный доступ к мутациям.

Это один из основных инструментов инспекции.

Базовый пример

import { useMutationState } from '@tanstack/react-query'

const mutations = useMutationState()

console.log(mutations)

Возвращается массив всех мутаций.


Структура объекта мутации

Каждая запись содержит большое количество информации.

Пример структуры:

[
  {
    mutationId: 1,
    state: {
      status: 'pending',
      data: undefined,
      error: null,
      variables: {
        title: 'New post'
      },
      submittedAt: 1710000000000
    },
    options: {
      mutationKey: ['posts', 'create']
    }
  }
]

Фильтрация мутаций

Фильтрация по mutationKey

const mutations = useMutationState({
  filters: {
    mutationKey: ['posts']
  }
})

TanStack Query выполнит частичное совпадение ключей.


Точное совпадение

const mutations = useMutationState({
  filters: {
    mutationKey: ['posts', 'create'],
    exact: true
  }
})

Фильтрация по статусу

Инспекция особенно полезна при анализе активных операций.

Только pending

const pendingMutations = useMutationState({
  filters: {
    status: 'pending'
  }
})

Только ошибки

const failedMutations = useMutationState({
  filters: {
    status: 'error'
  }
})

Успешные операции

const successfulMutations = useMutationState({
  filters: {
    status: 'success'
  }
})

sel ect в useMutationState

select позволяет извлекать только необходимые данные.

Получение variables

const variables = useMutationState({
  filters: {
    mutationKey: ['posts']
  },
  select: mutation => mutation.state.variables
})

Получение ошибок

const errors = useMutationState({
  filters: {
    status: 'error'
  },
  select: mutation => mutation.state.error
})

Получение времени отправки

const timestamps = useMutationState({
  select: mutation => mutation.state.submittedAt
})

Глобальный индикатор загрузки

Инспекция мутаций часто используется для отображения общего состояния приложения.

useIsMutating

import { useIsMutating } fr om '@tanstack/react-query'

const pendingCount = useIsMutating()

Возвращается количество активных мутаций.


Глобальный loader

function GlobalLoader() {
  const isMutating = useIsMutating()

  if (!isMutating) {
    return null
  }

  return <div>Saving...</div>
}

Фильтрация useIsMutating

По mutationKey

const pendingPosts = useIsMutating({
  mutationKey: ['posts']
})

Точное совпадение

const pendingCreate = useIsMutating({
  mutationKey: ['posts', 'create'],
  exact: true
})

Инспекция через queryClient

Иногда требуется доступ вне React-компонентов.

Для этого используется queryClient.


Получение MutationCache

const mutationCache = queryClient.getMutationCache()

Получение всех мутаций

const mutations = mutationCache.getAll()

console.log(mutations)

Поиск конкретной мутации

const mutation = mutationCache.find({
  mutationKey: ['posts', 'create']
})

Поиск нескольких мутаций

const mutations = mutationCache.findAll({
  mutationKey: ['posts']
})

Инспекция состояния мутации

Каждая мутация содержит внутреннее состояние.

const mutation = mutationCache.find({
  mutationKey: ['posts']
})

console.log(mutation.state)

Поля состояния мутации

Поле Назначение
status Текущий статус
data Результат
error Ошибка
variables Переданные параметры
context Контекст optimistic update
failureCount Количество ошибок
submittedAt Время запуска
isPaused Пауза retry

Наблюдение за MutationCache

MutationCache поддерживает подписки.

Это мощный механизм для DevTools и аналитики.


subscribe

const unsubscribe = mutationCache.subscribe(event => {
  console.log(event)
})

События MutationCache

TanStack Query генерирует события:

Событие Описание
added Мутация создана
removed Мутация удалена
updated Состояние изменилось
observerAdded Добавлен observer
observerRemoved Observer удалён

Анализ событий

mutationCache.subscribe(event => {
  if (event.type === 'updated') {
    console.log(event.mutation.state.status)
  }
})

Мониторинг ошибок

mutationCache.subscribe(event => {
  if (
    event.type === 'updated' &&
    event.mutation.state.status === 'error'
  ) {
    console.error(event.mutation.state.error)
  }
})

Построение логгера мутаций

mutationCache.subscribe(event => {
  if (event.type === 'updated') {
    console.log({
      key: event.mutation.options.mutationKey,
      status: event.mutation.state.status,
      variables: event.mutation.state.variables
    })
  }
})

Инспекция retry

Мутации поддерживают повторные попытки.

useMutation({
  mutationFn: savePost,
  retry: 3
})

Во время инспекции доступны:

mutation.state.failureCount

Анализ количества ошибок

const failed = useMutationState({
  select: mutation => ({
    failures: mutation.state.failureCount,
    error: mutation.state.error
  })
})

Инспекция optimistic updates

Optimistic update особенно важно анализировать при сложном UI.


Контекст optimistic update

useMutation({
  mutationFn: updatePost,

  onMutate: async variables => {
    return {
      previousPosts: []
    }
  }
})

Контекст сохраняется внутри:

mutation.state.context

Проверка optimistic context

const optimisticContexts = useMutationState({
  select: mutation => mutation.state.context
})

Инспекция variables

variables содержат параметры вызова мутации.

const variables = useMutationState({
  select: mutation => mutation.state.variables
})

Отслеживание отправленных данных

const pendingTitles = useMutationState({
  filters: {
    status: 'pending'
  },

  select: mutation => mutation.state.variables.title
})

Параллельные мутации

TanStack Query поддерживает одновременные мутации.

Инспекция помогает отслеживать конкурентные операции.


Анализ конкуренции

const pendingMutations = useMutationState({
  filters: {
    mutationKey: ['posts'],
    status: 'pending'
  }
})

Предотвращение дублирования

const pendingCount = useIsMutating({
  mutationKey: ['posts', 'create']
})

const disabled = pendingCount > 0

Инспекция paused мутаций

Мутации могут ставиться на паузу.

Например, при offline-режиме.

mutation.state.isPaused

Offline-first сценарии

const pausedMutations = useMutationState({
  select: mutation => ({
    paused: mutation.state.isPaused,
    variables: mutation.state.variables
  })
})

Инспекция времени мутации

submittedAt

const times = useMutationState({
  select: mutation => mutation.state.submittedAt
})

Вычисление длительности

const mutations = useMutationState({
  select: mutation => ({
    duration: Date.now() - mutation.state.submittedAt
  })
})

Devtools и инспекция

Пакет Devtools предоставляет визуальную инспекцию мутаций.

Установка:

npm install @tanstack/react-query-devtools

Подключение Devtools

import { ReactQueryDevtools } fr om '@tanstack/react-query-devtools'

function App() {
  return (
    <>
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}

Возможности Devtools для мутаций

Devtools позволяют:

  • просматривать mutation cache;
  • анализировать статусы;
  • отслеживать retry;
  • видеть variables;
  • анализировать optimistic updates;
  • инспектировать ошибки;
  • наблюдать lifecycle;
  • отслеживать paused mutations.

Инспекция ошибок

Получение stack trace

const errors = useMutationState({
  filters: {
    status: 'error'
  },

  select: mutation => ({
    message: mutation.state.error?.message,
    stack: mutation.state.error?.stack
  })
})

Централизованный error monitor

mutationCache.subscribe(event => {
  if (
    event.type === 'updated' &&
    event.mutation.state.status === 'error'
  ) {
    sendErrorToMonitoring(event.mutation.state.error)
  }
})

Инспекция mutation observers

Каждая мутация может иметь observers.

Это внутренние подписчики React-компонентов.


Количество observers

const mutation = mutationCache.find({
  mutationKey: ['posts']
})

console.log(mutation.getObserversCount())

Очистка мутаций

Mutation cache автоматически очищается.

Но доступна и ручная очистка.

mutationCache.clear()

Удаление конкретной мутации

mutationCache.remove(mutation)

GC мутаций

TanStack Query использует garbage collection.

После завершения и отсутствия observers мутация может быть удалена.


mutationKey как инструмент инспекции

Грамотно спроектированные ключи значительно упрощают анализ.


Плохая структура

mutationKey: ['save']

Недостатки:

  • невозможность точной фильтрации;
  • конфликт операций;
  • плохая диагностика.

Хорошая структура

mutationKey: ['posts', 'create']

или:

mutationKey: ['posts', 'update', postId]

Инспекция в больших приложениях

В крупных системах инспекция мутаций часто используется для:

  • telemetry;
  • аналитики;
  • аудита действий;
  • offline synchronization;
  • error tracking;
  • performance monitoring;
  • систем логирования;
  • диагностики race conditions.

Интеграция с системами мониторинга

mutationCache.subscribe(event => {
  if (event.type === 'updated') {
    analytics.track('mutation_updated', {
      key: event.mutation.options.mutationKey,
      status: event.mutation.state.status
    })
  }
})

Performance-анализ мутаций

mutationCache.subscribe(event => {
  if (
    event.type === 'updated' &&
    event.mutation.state.status === 'success'
  ) {
    const duration =
      Date.now() - event.mutation.state.submittedAt

    console.log('Mutation duration:', duration)
  }
})

Race conditions

Инспекция помогает находить гонки данных.

Пример проблемы:

updatePost({ title: 'A' })
updatePost({ title: 'B' })

При анализе pending mutations можно обнаруживать конкурирующие обновления.


Диагностика последовательности мутаций

const pending = useMutationState({
  filters: {
    mutationKey: ['posts', 'update'],
    status: 'pending'
  },

  select: mutation => ({
    id: mutation.mutationId,
    submittedAt: mutation.state.submittedAt,
    variables: mutation.state.variables
  })
})

Инспекция успешных ответов

const results = useMutationState({
  filters: {
    status: 'success'
  },

  select: mutation => mutation.state.data
})

Инспекция mutation meta

Мутации поддерживают пользовательские metadata.

useMutation({
  mutationFn: savePost,

  meta: {
    source: 'admin-panel'
  }
})

Получение meta

const meta = useMutationState({
  select: mutation => mutation.meta
})

Централизованный аудит действий

mutationCache.subscribe(event => {
  if (event.type === 'updated') {
    console.log({
      key: event.mutation.options.mutationKey,
      meta: event.mutation.meta,
      status: event.mutation.state.status
    })
  }
})

Инспекция состояния без React

TanStack Query не зависит от React.

Инспекция возможна в любом окружении JavaScript.

const queryClient = new QueryClient()

const mutations = queryClient
  .getMutationCache()
  .getAll()

Практика построения mutation inspector

Простейший инспектор:

function inspectMutations(queryClient) {
  const mutations =
    queryClient.getMutationCache().getAll()

  return mutations.map(mutation => ({
    key: mutation.options.mutationKey,
    status: mutation.state.status,
    variables: mutation.state.variables,
    error: mutation.state.error
  }))
}

Архитектурная роль инспекции

Инспекция мутаций в TanStack Query — это не просто инструмент отладки. Она формирует полноценный слой наблюдаемости серверных операций.

Через MutationCache, useMutationState, useIsMutating и подписки можно строить:

  • DevTools;
  • telemetry;
  • централизованные логгеры;
  • audit trail;
  • offline synchronization;
  • performance metrics;
  • error dashboards;
  • mutation queues;
  • системы диагностики конкурентных обновлений;
  • инструменты аналитики пользовательских действий.