如何重用 Redux Toolkit createSlice 函数中的 reducer 逻辑?
Posted
技术标签:
【中文标题】如何重用 Redux Toolkit createSlice 函数中的 reducer 逻辑?【英文标题】:How to reuse the reducer logic in Redux Toolkit createSlice function? 【发布时间】:2020-11-08 15:29:27 【问题描述】:我是 React 新手,我正在学习使用 React 来构建 Web 应用程序。我发现 Redux Toolkit 很有用,并使用它的 createSlice()
函数来实现基本功能。但是,我遇到了一个与“最佳实践”相关的问题,我不确定我是否正确构建了应用程序的架构。
假设我有一个 user
对象存储在 Redux 中。我创建了一个异步 thunk 函数来获取相关信息:
export const getUserInfo = createAsyncThunk('user/get', async (userId, thunkApi) =>
// fetching information using api
相应地,我处理pending/fulfilled/rejected
回调如下:
const userSlice = createSlice(
name: 'user',
initialState,
reducers:
setShowProgress(state, action: PayloadAction<boolean>)
state.showProgress = action.payload;
,
clearError(state)
state.error = null;
state.errorMessage = null;
,
extraReducers: builder =>
builder.addCase(getUserInfo.pending, (state, action) =>
// My question is here >_<
builder.addCase(getUserInfo.fulfilled, (state, action) =>
// handle data assignments
)
builder.addCase(getUserInfo.rejected, (state, action) =>
// handle error messages
)
)
考虑到修改显示状态标志在其他功能 api 实现中很常见,我将这两个函数(setShowProgress()
和 clearError()
)包装在 reducers
中。我的问题来了:如何引用getUserInfo.pending
函数中的两个函数?
虽然我可以只在getUserInfo.pending
中分配showProgress
和error
状态变量,而不是尝试调用reducer 函数,但是当我将来实现其他获取操作时肯定会引入重复代码。如果这不是推荐的模式,那么这种场景的最佳做法是什么?
【问题讨论】:
【参考方案1】:如果您的目标只是基于pending
/fulfilled
/rejected
设置loading
布尔值或error
属性,您可以使用1.4 版中引入的addMatcher
。
这是一个非常基本的示例,它使用泛型助手在多个切片中简化此操作。
// First, we'll just create some helpers in the event you do this in other slices. I'd export these from a util.
const hasPrefix = (action: AnyAction, prefix: string) =>
action.type.startsWith(prefix);
const isPending = (action: AnyAction) => action.type.endsWith("/pending");
const isFulfilled = (action: AnyAction) => action.type.endsWith("/fulfilled");
const isRejected = (action: AnyAction) => action.type.endsWith("/rejected");
const isPendingAction = (prefix: string) => (
action: AnyAction
): action is AnyAction => // Note: this cast to AnyAction could also be `any` or whatever fits your case best
return hasPrefix(action, prefix) && isPending(action);
;
const isRejectedAction = (prefix: string) => (
action: AnyAction
): action is AnyAction => // Note: this cast to AnyAction could also be `any` or whatever fits your case best - like if you had standardized errors and used `rejectWithValue`
return hasPrefix(action, prefix) && isRejected(action);
;
const isFulfilledAction = (prefix: string) => (
action: AnyAction
): action is AnyAction =>
return hasPrefix(action, prefix) && isFulfilled(action);
;
const userSlice = createSlice(
name: 'user',
initialState,
reducers: ,
extraReducers: builder =>
builder.addCase(getUserInfo.fulfilled, (state, action) =>
// handle data assignments
)
// use scoped matchers to handle generic loading / error setting behavior for async thunks this slice cares about
.addMatcher(isPendingAction("user/"), state =>
state.loading = true;
state.error = '';
)
.addMatcher(isRejectedAction("user/"), (state, action) =>
state.loading = false;
state.error = action.error; // or you could use `rejectWithValue` and pull it from the payload.
)
.addMatcher(isFulfilledAction("user/"), state =>
state.loading = false;
state.error = '';
);
)
【讨论】:
【参考方案2】:我在使用 redux-toolkit 中的 createReducer 替换旧的 switch case 方法时遇到了类似的问题。
在旧的方法中,我经常为许多操作使用一个通用的 reducer,如下所示:
const authentication = (state = initialState, action: AuthenticationActions) =>
switch (action.type)
case SIGN_UP_SUCCESS:
case SIGN_IN_SUCCESS:
return
...state,
account: action.payload,
;
case SIGN_OUT_SUCCESS:
return initialState;
default:
return state;
;
在新方法中,我必须创建两个相同的减速器并在.addCase
中使用它们。
所以我创建了一个通用助手来匹配多个动作:
import Action, AnyAction from '@reduxjs/toolkit';
declare interface TypedActionCreator<Type extends string>
(...args: any[]): Action<Type>;
type: Type;
const isOneOf = <ActionCreator extends TypedActionCreator<string>>(actions: ActionCreator[]) => (
(action: AnyAction): action is ReturnType<ActionCreator> => (
actions.map(( type ) => type).includes(action.type)
)
);
现在我们可以轻松地将它与.addMatcher
一起使用:
const authentication = createReducer(initialState, (builder) => (
builder
.addCase(signOutSuccess, () => (
...initialState,
))
.addMatcher(isOneOf([signInSuccess, signUpSuccess]), (state, action) => (
...state,
account: action.payload,
))
));
【讨论】:
以上是关于如何重用 Redux Toolkit createSlice 函数中的 reducer 逻辑?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Storybook 中使用 redux-toolkit?
如何将 redux-toolkit createSlice 与 React 类组件一起使用
如何从 Redux Toolkit 中去抖 createAsyncThunk
使用 @reduxjs/toolkit 中的 configureStore 时如何重置 Redux Store 的状态?