React+RTK动态Slice注入异常:懒加载模块Slice未加入RootReducer
问题:Webpack Module Federation + Redux Toolkit 动态加载Slice失败
我们基于 React + Redux Toolkit + RTK Query + Webpack Module Federation + Nx 技术栈开发应用,核心思路是将大型应用拆分为多个模块,创建全局Store并在模块懒加载时动态填充Reducer。
当前遇到的问题:加载模块时,Slice未成功添加至RootReducer。
目录结构
apps/ |--shell/ |--moneyback/ libs/ |--store/ |--auth/
现有代码实现
libs/store(全局Store,预期启动时为空)
export interface LazyLoadedSlices {} export const rootReducer = combineSlices(staticSlice).withLazyLoadedSlices<LazyLoadedSlices>(); export const dynamicMiddleware = createDynamicMiddleware(); export const store = configureStore({ devTools: process.env.ENV !== 'production', reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }).concat([ dynamicMiddleware.middleware, ]), });
libs/auth
import { rootReducer } from '@core-backoffice/store'; export const authSlice = createSlice({ name: 'auth', initialState, reducers: { addTokens: (state, { payload }: PayloadAction<PayloadLogin>) => { const { token, refresh } = payload; state.token = token; state.refresh = refresh; }, addUserData: (state, { payload }: PayloadAction<IUser>) => { const { email, fullName, roles, userId, workspaces } = payload; state.email = email; state.fullName = fullName; state.roles = roles; state.userId = userId; state.workspaces = workspaces; }, logout: () => initialState, }, selectors: { selectToken: (state) => state.token, selectRefreshToken: (state) => state.refresh, }, }); declare module '@core-backoffice/store' { export interface LazyLoadedSlices extends WithSlice<typeof authSlice> {} } const injectedSlice = authSlice.injectInto(rootReducer); export const { addTokens, logout, addUserData } = injectedSlice.actions; export const { selectRefreshToken, selectToken } = injectedSlice.selectors;
apps/shell
useAuth与useCheckAuth钩子中使用了useDispatch、useSelector和authApi
export function App() { useCheckAuth(); const location = useLocation(); const rootPath = useRootPath('/'); const token = useAppSelector((store) => store.auth?.token); const { logout } = useAuth(); return ( <React.Suspense fallback={null}> <Routes> <Route path="/" element={<NxWelcome title="shell" />} /> <Route path="/moneyback/*" element={<MoneybackApp />} /> <Route path="/login" element={<LoginPage />} /> </Routes> </React.Suspense> ); } export default App;
apps/moneyback/slice
apps/moneyback/app.tsx中同样使用了useDispatch、useSelector和servicesApi钩子,同时也用到了useAuth和useCheckAuth
import { rootReducer } from '@core-backoffice/store'; import { createSlice, WithSlice } from '@reduxjs/toolkit'; const initialState: { services: string[] } = { services: [], }; export const servicesSlice = createSlice({ name: 'servicesSlice', initialState, reducers: { addService: (state) => { state.services = [String(Math.random())]; }, }, selectors: { selectServices: (state) => state.services, }, }); declare module '@core-backoffice/store' { export interface LazyLoadedSlices extends WithSlice<typeof servicesSlice> {} } const injectedSlice = servicesSlice.injectInto(rootReducer); export const { addService } = injectedSlice.actions; export const { selectServices } = injectedSlice.selectors;
当前现象
- 应用启动后,authSlice和authApi已成功加入Store
- 切换至
localhost/moneyback时,moneyback模块懒加载,但servicesApi和servicesSlice未加入Store,调用dispatch(addServices)后Store中无对应Slice
所有代码均按照Redux Toolkit代码分割规范编写,预期效果为切换到moneyback模块时,其对应的Slice和API能自动加入全局Store,但未实现。
解决方案
修改libs/store代码,将Store、rootReducer和dynamicMiddleware挂载到全局window对象上,确保所有模块共享同一实例:
export interface LazyLoadedSlices {} const keyReduxStore = Symbol.for('reduxStore') as unknown as number; const keyRootReducer = Symbol.for('rootReducer') as unknown as number; const keyDynamicMiddleware = Symbol.for( 'dynamicMiddleware' ) as unknown as number; const combineReducers = () => { return combineSlices({}).withLazyLoadedSlices<LazyLoadedSlices>(); }; window[keyRootReducer] = window[keyRootReducer] || combineReducers(); export const rootReducer = window[keyRootReducer] as unknown as ReturnType< typeof combineReducers >; window[keyDynamicMiddleware] = window[keyDynamicMiddleware] || createDynamicMiddleware(); export const dynamicMiddleware = window[ keyDynamicMiddleware ] as unknown as DynamicMiddlewareInstance<any, Dispatch<UnknownAction>>; const setupStore = () => { return configureStore({ devTools: process.env.ENV !== 'production', reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(dynamicMiddleware.middleware), }); }; window[keyReduxStore] = window[keyReduxStore] || setupStore(); export const store = window[keyReduxStore] as unknown as AppStore; export type RootState = ReturnType<typeof rootReducer>; export type AppStore = ReturnType<typeof setupStore>; export type AppDispatch = AppStore['dispatch'];
内容的提问来源于stack exchange,提问作者kyrisss
相关产品推荐
相关产品推荐

