You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

大型React项目如何分步从Redux+Saga迁移至Redux Toolkit及RTK Query

Redux老项目逐步迁移Redux Toolkit方案

问题背景

现有大型React项目采用Redux + Saga + 异步reducer的redux-module架构:多个小型reducer合并为大型异步reducer,Saga也采用相同组织形式,希望在不一次性重写所有模块的前提下逐步迁移到Redux Toolkit和RTK Query,保证未迁移的模块可正常运行。

原有核心代码示例

  • 异步redux模块合并reducer代码
const reducer: (state: UserState, action: UserAction) => UserState = mergeReducers(
  userReducer,
  settingsReducer,
  signUpReducer
);
  • mergeReducers实现
const mergeReducers =
  (...reducers) =>
  (state, action) =>
    reducers.reduce((acc, reducer) => ({ ...acc, ...reducer(acc, action) }), state);
  • 标准reducer示例(注册相关)
const signUpReducer = (state: UserState = userInitialState, action: SignUpAction): UserState => {
  switch (action.type) {
    case SIGN_UP_CONTINUE_REQUESTED:
    case SIGN_UP_INITIATE_REQUESTED:
      return { ...state, pending: true, error: null };
    case SIGN_UP_INITIATE_SUCCEEDED:
      return { ...state, pending: false };
    case SIGN_UP_CONTINUE_SUCCEEDED:
      return {
        ...state,
        profile: action.payload.profile,
        token: action.payload.token,
        error: null,
      };
    case SIGN_UP_INITIATE_REJECTED:
    case SIGN_UP_CONTINUE_REJECTED:
      return { ...state, pending: false, error: action.payload };

    default:
      return { ...state };
  }
};
  • 异步模块生命周期逻辑
function startSaga(key, saga) {
  const runnableSaga = function* main() {
    const sagaTask = yield fork(saga);
    const { payload } = yield take(STOP_SAGA);

    if (payload === key) {
      yield cancel(sagaTask);
    }
  };

  sagaMiddleware.run(runnableSaga);
}

function stopSaga(key) {
  store.dispatch({
    payload: key,
    type: STOP_SAGA,
  });
}

export const useReduxModule = (key, reducer, saga, initialAction) => {
  useEffect(() => {
    if (!store.asyncReducers[key]) {
      store.injectReducer(key, reducer);
      startSaga(key, saga);
      if (initialAction) initialAction();
    }

    return () => {
      stopSaga(key);
      store.removeReducer(key);
    };
  }, []);
};
  • React侧模块使用方式
import { loadSomeDateRequested, reducer, saga } from './store';

const SomeComponent = ({ loadData }) => {
  useReduxModule(SOME_MODULE, reducer, saga, loadData);

  return (
    // some jsx
  );
};

export default connect(null, {
  loadData: loadSomeDateRequested,
})(SomeComponent);

SomeComponent.propTypes = {
  loadData: func.isRequired,
};
  • 原有Store配置
function createReducer(asyncReducers) {
  return combineReducers({
    ...staticReducers,
    ...asyncReducers,
  });
}

export const sagaMiddleware = createSagaMiddleware();

const bindMiddleware = (middlewares) =>
  (process.env.NODE_ENV !== 'production' && composeWithDevTools(applyMiddleware(...middlewares))) ||
  applyMiddleware(...middlewares);

export default function configureStore() {
  const store = createStore(createReducer(), bindMiddleware([sagaMiddleware]));
  store.asyncReducers = {};
  store.injectReducer = (key, asyncReducer) => {
    store.asyncReducers[key] = asyncReducer;
    store.replaceReducer(createReducer(store.asyncReducers));
  };
  store.removeReducer = (key) => {
    delete store.asyncReducers[key];
    delete store.getState()[key];
  };
  return store;
}

export const store = configureStore();
  • 静态reducer定义
export default {
  [MODULE_PDF_MODAL]: pdfModalReducer,
};

迁移方案

完全支持按模块逐步迁移,原有Redux逻辑和Redux Toolkit可以共存,不需要一次性重写所有代码。仅需要替换原有Store配置即可完成基础适配,原有所有业务逻辑可无改动正常运行。
改造后的Store配置代码如下:

import { configureStore } from '@reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import staticReducers from '@sharedStore/staticReducers';
import { combineReducers } from 'redux';

function createReducer(asyncReducers) {
  return combineReducers({
    ...staticReducers,
    ...asyncReducers,
  });
}

export const sagaMiddleware = createSagaMiddleware();

export default function configStore() {
  const store = configureStore({
    reducer: createReducer(),
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(sagaMiddleware),
  });
  store.asyncReducers = {};
  store.injectReducer = (key, asyncReducer) => {
    store.asyncReducers[key] = asyncReducer;
    store.replaceReducer(createReducer(store.asyncReducers));
  };
  store.removeReducer = (key) => {
    delete store.asyncReducers[key];
    delete store.getState()[key];
  };
  return store;
}

export const store = configStore();

补充说明:

  • 改造后原有所有Redux、Saga、异步模块逻辑可正常运行,无兼容问题
  • 后续新开发的模块可以直接用Redux Toolkit的createSlice、RTK Query实现,旧模块可以按需逐个迁移,迁移完成前两类逻辑会长期共存且运行稳定
  • 该方案完全兼容Redux原生的combineReducers方法,不需要修改原有异步模块的注入、卸载逻辑

内容的提问来源于stack exchange,提问作者Andrey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 10:54:03