RTK Query строит работу с серверными данными вокруг управляемого жизненного цикла запроса. Каждый запрос проходит набор последовательных стадий: инициализация, отправка, получение результата, кэширование, обновление, повторная загрузка и удаление из кэша.
Жизненный цикл является одной из центральных частей архитектуры RTK Query, поскольку именно он определяет:
RTK Query автоматизирует большую часть этой логики, скрывая сложные внутренние механизмы Redux.
Типичный запрос проходит следующие этапы:
Жизненный цикл начинается в момент вызова query hook.
const { data, isLoading } = useGetPostsQuery()
После вызова hook RTK Query:
Каждый запрос получает уникальный идентификатор.
Например:
useGetPostQuery(5)
RTK Query формирует cache key примерно такого вида:
getPost(5)
Если другой компонент выполнит такой же запрос:
useGetPostQuery(5)
RTK Query не станет отправлять второй HTTP-запрос, а использует уже существующий кэш.
Это один из важнейших механизмов оптимизации библиотеки.
После вычисления cache key RTK Query ищет данные в Redux store.
Если данные:
то сетевой запрос не выполняется.
Вместо этого данные сразу возвращаются компоненту.
Если данные отсутствуют или требуется обновление, RTK Query переводит запрос в состояние загрузки.
const { isLoading } = useGetPostsQuery()
Во время первой загрузки:
isLoading === true
После успешного завершения:
isLoading === false
RTK Query разделяет:
Используется только при первой загрузке, когда данных еще нет.
if (isLoading) {
return <Spinner />
}
Показывает любую активную сетевую операцию, включая refetch.
if (isFetching) {
console.log('Обновление данных')
}
Даже если данные уже существуют:
{
data: [...],
isLoading: false,
isFetching: true
}
Это позволяет отображать старые данные одновременно с обновлением.
После перехода в loading RTK Query вызывает baseQuery.
Обычно используется fetchBaseQuery:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: '/api'
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts'
})
})
})
Во время выполнения:
fetch('/api/posts')
Результат преобразуется во внутренний формат RTK Query.
Во время выполнения запроса endpoint находится в состоянии pending.
В store хранится информация:
{
status: 'pending'
}
RTK Query отслеживает:
После получения успешного ответа RTK Query:
RTK Query хранит результат запроса внутри slice API.
Упрощенно структура выглядит так:
{
api: {
queries: {
'getPosts(undefined)': {
status: 'fulfilled',
data: [...]
}
}
}
}
Кэш становится единым источником данных для всех компонентов.
После обновления store RTK Query инициирует повторный рендер компонентов.
const { data } = useGetPostsQuery()
После получения ответа:
data = [
{ id: 1, title: 'Post 1' }
]
Компонент автоматически получает новые данные без dispatch и reducer.
Если запрос завершился ошибкой:
const { error } = useGetPostsQuery()
Пример ошибки:
{
status: 404,
data: {
message: 'Not Found'
}
}
RTK Query поддерживает автоматический refetch.
Причины повторной загрузки:
При изменении аргумента формируется новый cache key.
useGetPostQuery(1)
Затем:
useGetPostQuery(2)
RTK Query считает это двумя независимыми запросами.
Можно автоматически выполнять повторный запрос при новом mount.
useGetPostsQuery(undefined, {
refetchOnMountOrArgChange: true
})
Теперь повторный mount вызовет refetch.
RTK Query умеет отслеживать состояние сети.
setupListeners(store.dispatch)
После восстановления интернета запросы могут автоматически обновляться.
refetchOnReconnect: true
RTK Query может обновлять данные при возврате пользователя в браузерную вкладку.
refetchOnFocus: true
Это особенно полезно для:
Любой query hook предоставляет функцию refetch.
const { refetch } = useGetPostsQuery()
Вызов:
await refetch()
запускает новый сетевой запрос независимо от состояния кэша.
Mutation имеют собственный жизненный цикл.
Пример:
const [createPost, result] = useCreatePostMutation()
После вызова:
await createPost({
title: 'New Post'
})
mutation проходит стадии:
Mutation часто запускают обновление query.
tagTypes: ['Posts']
Query:
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Posts']
})
Mutation:
createPost: builder.mutation({
query: (body) => ({
url: '/posts',
method: 'POST',
body
}),
invalidatesTags: ['Posts']
})
После успешной mutation RTK Query:
RTK Query удаляет неиспользуемые данные автоматически.
По умолчанию cache хранится:
60 секунд
после исчезновения последнего подписчика.
Время хранения можно изменить.
getPosts: builder.query({
query: () => '/posts',
keepUnusedDataFor: 300
})
Теперь кэш хранится:
300 секунд
RTK Query использует механизм счетчика подписчиков.
Если два компонента используют:
useGetPostsQuery()
то создается:
1 cache entry
2 subscribers
Когда один компонент размонтируется:
1 subscriber
Когда исчезает последний подписчик:
0 subscribers
После этого запускается таймер удаления cache entry.
После истечения keepUnusedDataFor RTK Query:
Следующий запрос снова выполнит HTTP-запрос.
RTK Query предоставляет lifecycle callbacks.
Основные:
Вызывается сразу после старта запроса.
getPost: builder.query({
query: (id) => `/posts/${id}`,
async onQueryStarted(arg, api) {
console.log('Запрос начался')
}
})
Используется для:
В callback доступны:
async onQueryStarted(arg, {
dispatch,
getState,
queryFulfilled,
requestId,
extra,
getCacheEntry
})
queryFulfilled содержит Promise результата запроса.
async onQueryStarted(arg, { queryFulfilled }) {
try {
const result = await queryFulfilled
console.log(result.data)
} catch (error) {
console.log(error)
}
}
RTK Query поддерживает optimistic update через updateQueryData.
async onQueryStarted(post, {
dispatch,
queryFulfilled
}) {
const patchResult = dispatch(
api.util.updateQueryData(
'getPosts',
undefined,
(draft) => {
draft.push(post)
}
)
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}
Механизм работает так:
Callback связан с жизненным циклом cache entry.
onCacheEntryAdded: async (
arg,
{
cacheDataLoaded,
cacheEntryRemoved
}
) => {
}
Promise завершается после появления данных в cache.
await cacheDataLoaded
Promise завершается после удаления cache entry.
await cacheEntryRemoved
Это позволяет корректно очищать ресурсы.
onCacheEntryAdded особенно полезен для WebSocket.
getMessages: builder.query({
query: () => '/messages',
async onCacheEntryAdded(
arg,
{
updateCachedData,
cacheDataLoaded,
cacheEntryRemoved
}
) {
await cacheDataLoaded
const socket = new WebSocket('ws://localhost')
socket.onmess age = (event) => {
const message = JSON.parse(event.data)
updateCachedData((draft) => {
draft.push(message)
})
}
await cacheEntryRemoved
socket.close()
}
})
Жизненный цикл:
RTK Query полностью интегрирован в Redux lifecycle.
Каждый запрос генерирует actions:
api/executeQuery/pending
api/executeQuery/fulfilled
api/executeQuery/rejected
Эти actions проходят через:
RTK Query автоматически предотвращает дублирующиеся запросы.
Если одновременно выполняются:
useGetPostsQuery()
в нескольких компонентах, библиотека:
RTK Query поддерживает периодическое обновление.
useGetPostsQuery(undefined, {
pollingInterval: 5000
})
Теперь запрос выполняется каждые:
5 секунд
Жизненный цикл становится циклическим:
fetch → cache update → wait → refetch
Жизненный цикл можно остановить.
useGetPostQuery(id, {
skip: !id
})
Пока условие истинно:
RTK Query поддерживает ручной запуск query.
const [trigger, result] = useLazyGetPostsQuery()
Запуск:
await trigger()
До вызова trigger:
RTK Query умеет отменять запросы.
Например:
Внутри используется AbortController.
После отмены запрос получает специальное состояние.
Это предотвращает:
RTK Query автоматически решает проблему гонок запросов.
Например:
Запрос A → 3 секунды
Запрос B → 1 секунда
Если B завершится раньше A, RTK Query корректно обработает порядок обновлений.
Жизненный цикл RTK Query подходит не только для HTTP.
Через onCacheEntryAdded можно поддерживать:
Кэш становится централизованным realtime-хранилищем.
RTK Query хранит несколько сущностей:
{
queries: {},
mutations: {},
subscriptions: {},
provided: {},
config: {}
}
Каждая часть участвует в жизненном цикле:
Типичный сценарий выглядит так:
Component mount
↓
Hook execution
↓
Cache lookup
↓
HTTP request
↓
pending
↓
fulfilled
↓
cache update
↓
UI rerender
↓
mutation
↓
tag invalidation
↓
refetch
↓
unused cache
↓
cache cleanup
Именно этот автоматизированный pipeline делает RTK Query полноценной системой управления серверным состоянием, а не просто инструментом для выполнения fetch-запросов.