大型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
相关产品推荐
相关产品推荐

