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

如何将Redux Store切片归组到同一属性下?

Redux Toolkit + TypeScript:分组切片时的类型兼容问题

我是Redux/Redux Toolkit新手,正在尝试组织Store状态,希望将多个切片归组到同一属性下,但遇到TypeScript类型兼容问题。

期望实现的效果

const fooSlice = createSlice(...);
const barSlice = createSlice(...);
const eggzSlice = createSlice(...);

const store = configureStore({
  reducer: {
    myGroupedSlice: {
      // 将这些归组到同一属性下
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    },
    eggz: eggzSlice.reducer
  }
});

// 类型化钩子(按文档实现)
type RootState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
type AppDispatch = typeof store.dispatch;
export const useAppDispatch: () => AppDispatch = useDispatch;

// 期望能这样使用选择器
const barValue = useAppSelector((state) => state.myGroupedSlice.bar);
const eggzValue = useAppSelector((state) => state.eggz);

尝试过的方案及问题

  • 将combineReducers作为myGroupedSlice的值:
    myGroupedSlice: combineReducers({ foo: fooSlice.reducer, bar: barSlice.reducer})
    
    添加eggz reducer时TypeScript报错,且两种情况下useAppSelector的类型都变为any。
  • 用combineReducers包裹所有reducer:
    reducer: combineReducers({ myGroupedSlice: combineReducers(...), eggz: eggzSlice.reducer})
    
    TypeScript无即时错误,但useAppSelector类型仍变为any。

补充说明

实验后发现,问题仅在添加中间件时出现,此时创建Store的代码如下:

export const listenerMiddleware = createListenerMiddleware();

const store = configureStore({
  reducer: {
    myGroupedSlice: combineReducers({
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    }),
    eggz: eggzSlice.reducer,
  },
  /* 
  中间件`getDefaultMiddleware`似乎将`myGroupedSlice`类型化为unknown,导致类型冲突
  */
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});

解决方案

问题出在createListenerMiddleware的类型推导和combineReducers的结合上,以下是几种可行的修复方式:

方案1:直接嵌套reducer(无需combineReducers)

Redux Toolkit的configureStore支持直接嵌套reducer对象,无需手动调用combineReducers,这种方式类型推导最准确:

const store = configureStore({
  reducer: {
    myGroupedSlice: {
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    },
    eggz: eggzSlice.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});

这种写法下,TypeScript能自动正确推导RootState的类型,useAppSelector也能正常获取类型提示。

方案2:给combineReducers显式指定泛型

如果必须使用combineReducers,可以手动定义分组状态类型并传递给泛型参数:

// 定义分组切片的状态类型
type MyGroupedState = {
  foo: ReturnType<typeof fooSlice.reducer>;
  bar: ReturnType<typeof barSlice.reducer>;
};

const store = configureStore({
  reducer: {
    myGroupedSlice: combineReducers<MyGroupedState>({
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    }),
    eggz: eggzSlice.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});

// 后续RootState和钩子定义保持不变
type RootState = ReturnType<typeof store.getState>;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

方案3:给listenerMiddleware指定RootState泛型

显式给createListenerMiddleware传递RootState类型,帮助TypeScript完成推导:

// 先定义完整的RootState类型
type RootState = {
  myGroupedSlice: {
    foo: ReturnType<typeof fooSlice.reducer>;
    bar: ReturnType<typeof barSlice.reducer>;
  };
  eggz: ReturnType<typeof eggzSlice.reducer>;
};

// 给listenerMiddleware指定泛型
export const listenerMiddleware = createListenerMiddleware<RootState>();

// 创建store
const store = configureStore({
  reducer: {
    myGroupedSlice: combineReducers({
      foo: fooSlice.reducer,
      bar: barSlice.reducer,
    }),
    eggz: eggzSlice.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().prepend(listenerMiddleware.middleware),
});

关键原因

当添加listenerMiddleware时,TypeScript需要明确知道RootState的类型才能正确推导中间件的类型。手动嵌套reducer时,configureStore能自动完成类型推导;而使用combineReducers时,类型推导会出现延迟,导致中间件无法正确识别状态类型,最终让useAppSelector的类型变为any或出现冲突。

内容的提问来源于stack exchange,提问作者Pedro Perpétua

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 23:46:29