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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 17:07:01