Интеграция с существующим Redux store

RTK Query проектируется как надстройка над Redux Toolkit и не требует отдельного состояния вне Redux store. Его ключевая особенность заключается в том, что он встраивается в уже существующую структуру стора как дополнительный “слайс”, который управляет кэшем запросов, состоянием загрузки и метаданными API-вызовов.

Интеграция с существующим Redux store сводится к добавлению API reducer-а, middleware и правильной конфигурации store без необходимости переписывать уже существующие reducers.


Подключение RTK Query к существующему store

Любая интеграция начинается с расширения конфигурации 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 reducer
  • api.middleware обязателен для работы кэша, дедупликации и инвалидации запросов
  • существующие reducers не требуют изменений
  • порядок подключения middleware важен: RTK Query middleware добавляется после стандартных middleware

Интеграция с уже существующей архитектурой Redux

В проектах с устоявшейся архитектурой Redux часто присутствуют:

  • feature-based slices
  • custom middleware (логирование, аналитика)
  • persisted state (redux-persist)
  • серверный state через thunk или saga

RTK Query может сосуществовать с любой из этих схем.


Совместное использование RTK Query и обычных slices

Типичная ошибка при миграции — попытка перенести всё состояние в RTK Query. Однако RTK Query предназначен только для server state.

Разделение ответственности

  • RTK Query:

    • данные с сервера
    • кэш запросов
    • статусы загрузки
  • Redux slices:

    • UI state
    • локальные состояния форм
    • клиентская логика
    • временные флаги

Пример комбинированного подхода:

const store = configureStore({
  reducer: {
    auth: authReducer,
    ui: uiReducer,
    [api.reducerPath]: api.reducer
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(api.middleware)
})

Интеграция с redux-persist

При использовании redux-persist важно понимать, что RTK Query кэш обычно не должен персиститься полностью.

Проблема

RTK Query хранит:

  • результаты запросов
  • метаданные
  • состояние подписок

Персистирование может привести к:

  • устаревшим данным
  • конфликтам rehydration
  • неконсистентности кэша

Рекомендуемый подход

Исключение 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 slices

В крупных приложениях допускается несколько API-сервисов:

  • публичное API
  • админское 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
}

Middleware и его роль в существующем store

RTK Query middleware выполняет критически важные функции:

  • запуск запросов
  • кэширование результатов
  • автоматическая рефетч-логика
  • дедупликация запросов
  • управление подписками

При интеграции важно не нарушить цепочку middleware:

middleware: (getDefaultMiddleware) =>
  getDefaultMiddleware({
    serializableCheck: false
  }).concat(api.middleware)

Важный нюанс

Если в проекте уже есть кастомные middleware (например, логирование), порядок может влиять на поведение:

  • middleware до RTK Query может не видеть итоговые actions
  • middleware после RTK Query видит уже “обработанные” actions

Интеграция с существующими thunk и saga

RTK Query не требует удаления:

  • redux-thunk
  • redux-saga

Сосуществование с thunk

Thunk может использоваться для:

  • сложной бизнес-логики
  • цепочек действий
  • side effects вне HTTP

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))
  }
}

Доступ к API state в существующих reducers

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

Интеграция с DevTools и отладкой существующего store

RTK Query полностью совместим с Redux DevTools. В store появляются:

  • actions запросов
  • cache updates
  • lifecycle events

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

  • api/executeQuery/pending
  • api/executeQuery/fulfilled
  • api/updateQueryData

В больших приложениях это позволяет наблюдать полный цикл server state без дополнительных инструментов.


Расширение существующих reducers через invalidateTags

RTK Query может взаимодействовать с уже существующим Redux state через механизмы инвалидации.

endpoints: (builder) => ({
  updateUser: builder.mutation({
    query: (data) => ({
      url: '/user',
      method: 'PUT',
      body: data
    }),
    invalidatesTags: ['User']
  })
})

Это позволяет синхронизировать server state с legacy reducers без ручного обновления store.


Типичные архитектурные ошибки при интеграции

Смешивание server state и client state

Частая проблема — попытка заменить существующие UI reducers на RTK Query.

Дублирование источников данных

Одна и та же сущность одновременно хранится:

  • в slice
  • в RTK Query cache

Это приводит к рассинхронизации.

Игнорирование middleware

Без подключения api.middleware RTK Query превращается в “мертвый” reducer без функциональности.

Персистирование кэша без стратегии

Полный persist RTK Query store почти всегда ухудшает предсказуемость состояния.


Миграционный сценарий без переписывания store

RTK Query может быть внедрён инкрементально:

  1. Добавляется API slice
  2. Подключается reducer и middleware
  3. Новые фичи используют RTK Query
  4. Старые thunk остаются без изменений
  5. Постепенная замена legacy запросов

При этом store остаётся стабильным, а изменения происходят на уровне API слоя.


Взаимодействие с селекторами существующих slices

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.


Итоговая структура store в интегрированном приложении

Типичная конфигурация после внедрения RTK Query выглядит как гибридная модель:

  • domain slices (auth, ui, settings)
  • RTK Query API slices (server state)
  • middleware chain (thunk/saga + api.middleware)
  • optional persistence layer с исключением API cache

Такая архитектура сохраняет совместимость с существующим Redux-кодом и постепенно переводит работу с серверными данными в декларативную модель RTK Query без разрушения текущей структуры приложения.