RTK Query проектируется как надстройка над Redux Toolkit и не требует отдельного состояния вне Redux store. Его ключевая особенность заключается в том, что он встраивается в уже существующую структуру стора как дополнительный “слайс”, который управляет кэшем запросов, состоянием загрузки и метаданными API-вызовов.
Интеграция с существующим Redux store сводится к добавлению API reducer-а, middleware и правильной конфигурации store без необходимости переписывать уже существующие reducers.
Любая интеграция начинается с расширения конфигурации store. Если приложение уже использует несколько reducers, RTK Query добавляется как дополнительный reducer на верхнем уровне.
import { configureStore } from '@reduxjs/toolkit'
import { api } from './services/api'
import userReducer from './features/user/userSlice'
import authReducer from './features/auth/authSlice'
export const store = configureStore({
reducer: {
user: userReducer,
auth: authReducer,
[api.reducerPath]: api.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware)
})
api.reducerPath должен быть добавлен в root
reducerapi.middleware обязателен для работы кэша, дедупликации
и инвалидации запросовВ проектах с устоявшейся архитектурой Redux часто присутствуют:
RTK Query может сосуществовать с любой из этих схем.
Типичная ошибка при миграции — попытка перенести всё состояние в RTK Query. Однако RTK Query предназначен только для server state.
RTK Query:
Redux slices:
Пример комбинированного подхода:
const store = configureStore({
reducer: {
auth: authReducer,
ui: uiReducer,
[api.reducerPath]: api.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware)
})
При использовании redux-persist важно понимать, что RTK
Query кэш обычно не должен персиститься полностью.
RTK Query хранит:
Персистирование может привести к:
Исключение API slice из persist configuration:
import storage from 'redux-persist/lib/storage'
import { persistReducer } from 'redux-persist'
import authReducer from './authSlice'
import { api } from '../services/api'
const persistConfig = {
key: 'root',
storage,
blacklist: [api.reducerPath]
}
const rootReducer = {
auth: persistReducer(persistConfig, authReducer),
[api.reducerPath]: api.reducer
}
В крупных приложениях допускается несколько API-сервисов:
Каждый создаётся через createApi:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const publicApi = createApi({
reducerPath: 'publicApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api/public' }),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => '/posts'
})
})
})
И добавляется в store аналогично:
reducer: {
[publicApi.reducerPath]: publicApi.reducer,
[adminApi.reducerPath]: adminApi.reducer
}
RTK Query middleware выполняет критически важные функции:
При интеграции важно не нарушить цепочку middleware:
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false
}).concat(api.middleware)
Если в проекте уже есть кастомные middleware (например, логирование), порядок может влиять на поведение:
RTK Query не требует удаления:
Thunk может использоваться для:
RTK Query — для декларативных запросов.
Пример гибридной логики:
export const loginThunk = (credentials) => async (dispatch, getState, api) => {
const result = await dispatch(
api.endpoints.login.initiate(credentials)
)
if (result.data) {
dispatch(setAuth(result.data))
}
}
RTK Query хранит состояние в state[api.reducerPath]. Это
позволяет интегрироваться с обычными selectors.
const selectApiState = (state) => state[api.reducerPath]
const selectCachedUsers = (state) =>
state[api.reducerPath].queries['getUsers(undefined)']?.data
Однако прямой доступ к внутренностям кэша считается хрупким. Более устойчивый подход — использование auto-generated selectors:
export const {
useGetUsersQuery,
useGetUserByIdQuery
} = api
RTK Query полностью совместим с Redux DevTools. В store появляются:
Типичный поток:
api/executeQuery/pendingapi/executeQuery/fulfilledapi/updateQueryDataВ больших приложениях это позволяет наблюдать полный цикл server state без дополнительных инструментов.
RTK Query может взаимодействовать с уже существующим Redux state через механизмы инвалидации.
endpoints: (builder) => ({
updateUser: builder.mutation({
query: (data) => ({
url: '/user',
method: 'PUT',
body: data
}),
invalidatesTags: ['User']
})
})
Это позволяет синхронизировать server state с legacy reducers без ручного обновления store.
Частая проблема — попытка заменить существующие UI reducers на RTK Query.
Одна и та же сущность одновременно хранится:
Это приводит к рассинхронизации.
Без подключения api.middleware RTK Query превращается в
“мертвый” reducer без функциональности.
Полный persist RTK Query store почти всегда ухудшает предсказуемость состояния.
RTK Query может быть внедрён инкрементально:
При этом store остаётся стабильным, а изменения происходят на уровне API слоя.
RTK Query легко комбинируется с memoized selectors:
import { createSelector } from '@reduxjs/toolkit'
const selectAuth = (state) => state.auth
const selectUserQuery = (state) =>
state.api.queries['getUser(undefined)']
export const selectUserProfile = createSelector(
[selectAuth, selectUserQuery],
(auth, userQuery) => ({
user: userQuery?.data,
isAuthenticated: auth.isAuthenticated
})
)
Такой подход позволяет объединять legacy state и RTK Query cache в единые derived selectors.
Типичная конфигурация после внедрения RTK Query выглядит как гибридная модель:
Такая архитектура сохраняет совместимость с существующим Redux-кодом и постепенно переводит работу с серверными данными в декларативную модель RTK Query без разрушения текущей структуры приложения.