【问题标题】:How to reuse the reducer logic in Redux Toolkit createSlice function?如何重用 Redux Toolkit createSlice 函数中的 reducer 逻辑?
【发布时间】: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 中分配showProgresserror 状态变量,而不是尝试调用reducer 函数,但是当我将来实现其他获取操作时肯定会引入重复代码。如果这不是推荐的模式,那么这种场景的最佳做法是什么?

【问题讨论】:

    标签: reactjs redux react-redux redux-toolkit


    【解决方案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,
          }))
      ));
      

      【讨论】:

        猜你喜欢
        • 2021-04-06
        • 2020-04-24
        • 2021-04-14
        • 1970-01-01
        • 2018-10-14
        • 2020-07-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多