首页 > 解决方案 > 如何重用 Redux Toolkit createSlice 函数中的 reducer 逻辑?

问题描述

我是 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函数?

虽然我可以只分配showProgresserror状态变量getUserInfo.pending而不是尝试调用 reducer 函数,但当我将来实现其他获取操作时,这肯定会引入重复代码。如果这不是推荐的模式,那么这种场景的最佳实践是什么?

标签: reactjsreduxreact-reduxredux-toolkit

解决方案


如果您的目标只是基于//设置loading布尔值或属性,则可以使用1.4 版中引入的。errorpendingfulfilledrejectedaddMatcher

这是一个非常基本的示例,它使用通用帮助器在多个切片中简化此操作。

// 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 = '';
      });
    }
})

推荐阅读