Redux Toolkit Slice扩展:如何复用或重写extraReducers?
几个月前我就遇到了这个难题,目前正在尝试扩展Redux Toolkit的Slice:已经定义好一个Slice,希望添加或重写部分reducers,现在已经通过自定义extendSlice函数实现了caseReducers的扩展,但无法访问Slice内部的extraReducers,想请教两个问题:
- 能否像处理caseReducers那样扩展/重写extraReducers?
- extraReducers是否包含在caseReducers中?
附已实现的extendSlice函数代码:
//Extend slice function /** * @remarks * * This method extends an already declared slice in oreder to attach custom actions to the reducer. * * @param slice The root slice you want to extend * @param caseReducers The case reducers you want to add to the current ones. Current slice reducers won't be override. * @param extraReducers Extra reducers to add to the slice. Don't use this for slices that contain extra reducers! They will be overridden! * * * @returns slice: Slice * The extended slice containing the current reducers concatenated with the new caseReducers and with the extraReducers * */ export const extendSlice = ( slice: Slice, caseReducers?: ValidateSliceCaseReducers<any, SliceCaseReducers<any>>, extraReducers?: ( ( builder: ActionReducerMapBuilder<NoInfer<any>> ) => void ) ) => { return createSlice( { name: slice.name, initialState: slice.getInitialState(), reducers: { ...caseReducers, ...slice.caseReducers }, extraReducers } ); };
解答
问题2:extraReducers是否包含在caseReducers中?
完全不包含。Redux Toolkit里caseReducers是对应当前Slice自身reducers字段生成的action的处理逻辑,而extraReducers是专门用来处理外部action(比如其他Slice的action、createAsyncThunk生成的异步action等)的,二者是完全独立的结构,在Slice实例中分开存储,你没法从slice.caseReducers里拿到extraReducers的内容。
问题1:能否像处理caseReducers那样扩展/重写extraReducers?
可以实现,但不能直接像合并caseReducers那样操作——因为createSlice执行后,原Slice的extraReducers已经被编译进最终的reducer函数里,并没有保留原始的builder回调或配置对象,需要换两种思路来实现:
方案1:提前抽离原Slice的extraReducers逻辑
如果能控制原Slice的创建流程,建议把原Slice的extraReducers单独写成一个独立函数,这样扩展时可以直接复用并追加新逻辑:
// 抽离原Slice的extraReducers逻辑 const originalExtraReducers = (builder: ActionReducerMapBuilder<MyState>) => { builder.addCase(otherSliceAction, (state, action) => { // 原extraReducer逻辑 }); }; // 创建原Slice时传入这个抽离的函数 const originalSlice = createSlice({ name: 'original', initialState: myInitialState, reducers: {}, extraReducers: originalExtraReducers }); // 修改extendSlice函数,支持合并extraReducers export const extendSlice = ( slice: Slice & { extraReducers?: (builder: ActionReducerMapBuilder<any>) => void }, caseReducers?: ValidateSliceCaseReducers<any, SliceCaseReducers<any>>, extraReducers?: (builder: ActionReducerMapBuilder<NoInfer<any>>) => void ) => { return createSlice({ name: slice.name, initialState: slice.getInitialState(), // 注意顺序:新的caseReducers放后面可以覆盖原有的同名reducer reducers: { ...slice.caseReducers, ...caseReducers }, extraReducers: (builder) => { // 先执行原Slice的extraReducers逻辑 slice.extraReducers?.(builder); // 再执行新的extraReducers逻辑 extraReducers?.(builder); } }); }; // 使用示例:扩展时追加新的extraReducer const extendedSlice = extendSlice(originalSlice, newCaseReducers, (builder) => { builder.addCase(newExternalAction, (state, action) => { // 新增的extraReducer逻辑 }); });
方案2:合并原reducer与新extraReducer逻辑
如果没法提前抽离原Slice的extraReducers,也可以直接把原Slice的reducer和新的extraReducer逻辑合并:
import { createReducer, AnyAction } from '@reduxjs/toolkit'; export const extendSlice = ( slice: Slice, caseReducers?: ValidateSliceCaseReducers<any, SliceCaseReducers<any>>, extraReducers?: (builder: ActionReducerMapBuilder<NoInfer<any>>) => void ) => { // 创建包含新caseReducers的基础slice const baseSlice = createSlice({ name: slice.name, initialState: slice.getInitialState(), reducers: { ...slice.caseReducers, ...caseReducers } }); // 生成新extraReducer对应的reducer函数 const newExtraReducer = extraReducers ? createReducer(slice.getInitialState(), extraReducers) : (state) => state; // 合并原reducer和新extraReducer:先执行原逻辑,再执行新逻辑(新逻辑会覆盖同action的原处理) const combinedReducer = (state: any, action: AnyAction) => { const updatedState = slice.reducer(state, action); return newExtraReducer(updatedState, action); }; // 返回替换了reducer的Slice对象 return { ...baseSlice, reducer: combinedReducer }; };
这种方式下,如果新的extraReducer和原Slice的extraReducer处理同一个action,新的逻辑会覆盖原有的,因为它是后执行的。
内容的提问来源于stack exchange,提问作者Andy95

