RTK Query предоставляет встроенную инфраструктуру для отслеживания сетевой активности, состояния кэша, жизненного цикла запросов и производительности API-вызовов. При разработке сложных приложений мониторинг становится критически важным элементом архитектуры, поскольку позволяет:
RTK Query построен поверх Redux Toolkit, поэтому все операции отражаются через Redux Store и могут быть проанализированы средствами middleware, Redux DevTools и кастомных инструментов логирования.
Все данные RTK Query хранятся внутри reducer, зарегистрированного
через createApi.
Пример:
import { configureStore } from '@reduxjs/toolkit'
import { api } from './services/api'
export const store = configureStore({
reducer: {
[api.reducerPath]: api.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware)
})
Состояние RTK Query содержит несколько ключевых разделов:
state.api = {
queries: {},
mutations: {},
provided: {},
subscriptions: {},
config: {}
}
Каждый раздел участвует в мониторинге поведения системы.
Раздел queries содержит информацию обо всех
query-запросах.
Пример структуры:
queries: {
'getUsers(undefined)': {
status: 'fulfilled',
endpointName: 'getUsers',
requestId: 'abc123',
startedTimeStamp: 171000000,
fulfilledTimeStamp: 171000300,
data: [...],
error: undefined
}
}
Ключевые поля:
| Поле | Назначение |
|---|---|
| status | Состояние запроса |
| endpointName | Имя endpoint |
| requestId | Идентификатор запроса |
| startedTimeStamp | Время начала |
| fulfilledTimeStamp | Время завершения |
| data | Кэшированные данные |
| error | Информация об ошибке |
RTK Query автоматически сохраняет временные метки начала и завершения запросов.
Расчет длительности:
const query =
store.getState().api.queries['getUsers(undefined)']
const duration =
query.fulfilledTimeStamp - query.startedTimeStamp
console.log(duration)
Это позволяет:
Наиболее гибкий способ сбора метрик — кастомный middleware.
Пример middleware:
const monitoringMiddleware =
(store) => (next) => (action) => {
const result = next(action)
if (action.type.endsWith('/fulfilled')) {
console.log('SUCCESS:', action.type)
}
if (action.type.endsWith('/rejected')) {
console.log('ERROR:', action.type)
}
return result
}
Подключение:
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(api.middleware)
.concat(monitoringMiddleware)
RTK Query генерирует большое количество action:
| Action | Назначение |
|---|---|
| executeQuery/pending | Начало запроса |
| executeQuery/fulfilled | Успешный ответ |
| executeQuery/rejected | Ошибка |
| invalidateTags | Инвалидация кэша |
| removeQueryResult | Очистка |
| subscriptionsUpdated | Изменение подписок |
Пример анализа:
const monitoringMiddleware =
() => (next) => (action) => {
if (action.type.includes('executeQuery')) {
console.log(action)
}
return next(action)
}
Часто требуется анализировать процент успешных запросов.
Пример счетчиков:
const metrics = {
success: 0,
failed: 0
}
const monitoringMiddleware =
() => (next) => (action) => {
if (action.type.endsWith('/fulfilled')) {
metrics.success++
}
if (action.type.endsWith('/rejected')) {
metrics.failed++
}
return next(action)
}
Расчет error rate:
const errorRate =
metrics.failed /
(metrics.success + metrics.failed)
RTK Query позволяет выявлять избыточные сетевые обращения.
Пример:
const requests = {}
const monitoringMiddleware =
() => (next) => (action) => {
if (action.type.endsWith('/pending')) {
const endpoint =
action.meta?.arg?.endpointName
requests[endpoint] =
(requests[endpoint] || 0) + 1
}
return next(action)
}
Это помогает обнаружить:
RTK Query активно использует кэширование.
Эффективность кэша — важная метрика производительности.
Пример анализа:
const state = store.getState()
const queries =
Object.values(state.api.queries)
const cachedQueries =
queries.filter(
query => query.status === 'fulfilled'
)
console.log(cachedQueries.length)
Неверная invalidation-стратегия способна создавать огромную нагрузку на API.
Пример:
invalidatesTags: ['Users']
Если тег инвалидируется слишком часто:
Мониторинг invalidation:
const monitoringMiddleware =
() => (next) => (action) => {
if (action.type.includes('invalidateTags')) {
console.log('INVALIDATION:', action)
}
return next(action)
}
Polling может быть серьезным источником нагрузки.
Пример:
useGetUsersQuery(undefined, {
pollingInterval: 5000
})
При большом количестве вкладок:
Мониторинг polling:
const pollingStats = {}
const middleware =
() => (next) => (action) => {
if (action.type.endsWith('/pending')) {
const endpoint =
action.meta.arg.endpointName
pollingStats[endpoint] =
(pollingStats[endpoint] || 0) + 1
}
return next(action)
}
RTK Query отслеживает активные subscriptions.
Пример состояния:
subscriptions: {
'getUsers(undefined)': {
'component-id-1': {},
'component-id-2': {}
}
}
Это позволяет анализировать:
Одна из распространенных проблем — накопление query cache.
Контроль количества query:
const state = store.getState()
const queryCount =
Object.keys(state.api.queries).length
console.log(queryCount)
Если количество запросов постоянно растет — вероятны:
RTK Query строит cache key на основе аргументов.
Проблемный пример:
useGetUserQuery({
id: 1,
timestamp: Date.now()
})
Каждый вызов создает новый cache key.
Последствия:
Диагностика:
const queries =
Object.keys(store.getState().api.queries)
console.log(queries)
RTK Query поддерживает retry через wrapper.
Пример:
import {
retry,
fetchBaseQuery
} from '@reduxjs/toolkit/query/react'
const baseQuery = retry(
fetchBaseQuery({
baseUrl: '/api'
}),
{
maxRetries: 5
}
)
Мониторинг retry:
const middleware =
() => (next) => (action) => {
if (action.meta?.baseQueryMeta) {
console.log(action.meta.baseQueryMeta)
}
return next(action)
}
RTK Query предоставляет подробную информацию об ошибках.
Пример:
const { error } = useGetUsersQuery()
Структура:
{
status: 500,
data: {
message: 'Internal Server Error'
}
}
Централизованный мониторинг:
const middleware =
() => (next) => (action) => {
if (action.type.endsWith('/rejected')) {
console.error({
endpoint:
action.meta.arg.endpointName,
error:
action.error
})
}
return next(action)
}
RTK Query удобно интегрируется с:
Пример интеграции с Sentry:
import * as Sentry from '@sentry/react'
const middleware =
() => (next) => (action) => {
if (action.type.endsWith('/rejected')) {
Sentry.captureException(action.error)
}
return next(action)
}
RTK Query позволяет анализировать frontend latency.
Пример измерения:
const middleware =
() => (next) => (action) => {
if (action.type.endsWith('/fulfilled')) {
const started =
action.meta.startedTimeStamp
const finished =
Date.now()
console.log(
finished - started
)
}
return next(action)
}
RTK Query автоматически удаляет неиспользуемые данные.
Пример:
keepUnusedDataFor: 60
Мониторинг времени жизни:
const state = store.getState()
for (const query of Object.values(state.api.queries)) {
console.log({
endpoint: query.endpointName,
started: query.startedTimeStamp
})
}
RTK Query поддерживает:
Пример:
useGetUsersQuery(undefined, {
refetchOnFocus: true
})
При большом количестве вкладок возможны всплески трафика.
Мониторинг:
const middleware =
() => (next) => (action) => {
if (
action.type.includes('refetch')
) {
console.log(action)
}
return next(action)
}
Optimistic update может вызывать рассинхронизацию данных.
Пример:
onQueryStarted(id, { dispatch, queryFulfilled }) {
const patch =
dispatch(
api.util.updateQueryData(
'getUsers',
undefined,
draft => {
draft.push({
id
})
}
)
)
try {
await queryFulfilled
} catch {
patch.undo()
}
}
Мониторинг rollback:
catch (error) {
console.error('ROLLBACK', error)
}
Иногда требуется определить наиболее нагруженные endpoint.
Пример агрегатора:
const endpointStats = {}
const middleware =
() => (next) => (action) => {
const endpoint =
action.meta?.arg?.endpointName
if (endpoint) {
endpointStats[endpoint] =
(endpointStats[endpoint] || 0) + 1
}
return next(action)
}
Можно построить внутреннюю dashboard-систему.
Пример структуры:
const metrics = {
totalRequests: 0,
failedRequests: 0,
avgResponseTime: 0,
cacheHits: 0,
cacheMisses: 0
}
Обновление метрик:
const middleware =
() => (next) => (action) => {
if (action.type.endsWith('/fulfilled')) {
metrics.totalRequests++
}
if (action.type.endsWith('/rejected')) {
metrics.failedRequests++
}
return next(action)
}
Redux DevTools позволяет:
Особенно полезен анализ последовательностей:
pending
fulfilled
invalidateTags
refetch
fulfilled
Подобные цепочки позволяют выявлять проблемы архитектуры API.
RTK Query влияет не только на сеть, но и на render performance.
Проблемы:
Мониторинг render count:
const renders = useRef(0)
renders.current++
console.log(renders.current)
RTK Query использует memoized selector.
Плохая практика:
const result = useGetUsersQuery()
const users =
result.data?.map(user => ({
...user
}))
Каждый render создает новые ссылки.
Лучше:
const { users } =
api.useGetUsersQuery(undefined, {
selectFromResult: ({ data }) => ({
users: data
})
})
Особенно опасны ситуации массового refetch.
Пример сценария:
invalidatesTags: ['Users']
При обновлении одного пользователя:
Мониторинг burst:
const requests = []
const middleware =
() => (next) => (action) => {
if (action.type.endsWith('/pending')) {
requests.push(Date.now())
}
return next(action)
}
Крупные приложения часто отправляют telemetry на backend.
Пример структуры события:
{
endpoint: 'getUsers',
duration: 230,
success: true,
timestamp: Date.now()
}
Отправка:
fetch('/metrics', {
method: 'POST',
body: JSON.stringify(metrics)
})
RTK Query умеет автоматически refetch после reconnect.
Пример:
setupListeners(store.dispatch)
Мониторинг reconnect:
window.addEventListener('online', () => {
console.log('ONLINE')
})
RTK Query способен незаметно перегружать API при:
Мониторинг позволяет выявлять: