【问题标题】:combine extraReducers with exact same code with redux toolkit将 extraReducers 与完全相同的代码与 redux 工具包相结合
【发布时间】:2021-04-02 21:05:11
【问题描述】:

我使用 redux 工具包创建了一个全局错误处理切片。而且我想对其进行重构以使其更“干”:

const errorsSlice = createSlice({
  name: "error",
  initialState,
  reducers: {
    clearError: (state) => {
      state.errors = null;
      state.isOpen = false;
    },
  },
  extraReducers: {
    [createScript.rejected]: (state, { payload }) => {
      const errorArray = Object.values(payload.message).map(
        (key) => key.message
      );
      state.errors = errorArray;
      state.isOpen = true;
    },
    [createScene.rejected]: (state, { payload }) => {
      const errorArray = Object.values(payload.message).map(
        (key) => key.message
      );
      state.errors = errorArray;
      state.isOpen = true;
    },
  },
});

两个 extraReducer 做完全相同的事情,并且有效负载被标准化。我的代码可以正常工作。

有没有办法将 2 组合成一个 extraReducer(最后会更多)?

【问题讨论】:

    标签: javascript reactjs redux-toolkit


    【解决方案1】:

    我不相信有办法将两个 case reducer 组合成一个 case reducer,但是您当然可以为每个 case reducer 提供相同的 reducer 功能。将重复的 reducer 函数重构为一个通用的 reducer 函数。

    const rejectionReducer = (state, { payload }) => {
      const errorArray = Object.values(payload.message).map(
        (key) => key.message
      );
      state.errors = errorArray;
      state.isOpen = true;
    };
    
    ...
    
    const errorsSlice = createSlice({
      name: "error",
      initialState,
      reducers: {
        clearError: (state) => {
          state.errors = null;
          state.isOpen = false;
        },
      },
      extraReducers: {
        [createScript.rejected]: rejectionReducer,
        [createScene.rejected]: rejectionReducer,
    });
    

    更新

    使用 isRejectedWithValue 高阶函数,您可以将 thunk 操作组合成匹配​​器。

    import { isRejectedWithValue } from '@reduxjs/toolkit';
    
    const rejectionReducer = (state, { payload }) => {
      const errorArray = Object.values(payload.message).map(
        (key) => key.message
      );
      state.errors = errorArray;
      state.isOpen = true;
    };
    
    const errorsSlice = createSlice({
      name: "error",
      initialState,
      reducers: {
        clearError: (state) => {
          state.errors = null;
          state.isOpen = false;
        },
      },
      extraReducers: builder => {
        builder.addMatcher(
          isRejectedWithValue(createScript, createScene), // <-- thunk actions
          rejectionReducer
        );
      },
    });
    

    【讨论】:

    • @PixAff 啊,很好的发现。我想我理解这一切是如何工作的,但 isRejectedWithValue 高阶函数似乎只有在你的 thunk 使用 rejectWithValue 动作实用程序 Handling thunk errors 时才有效。看起来isRejectedWithValue 可以组合多个动作并将错误放在payload 属性上,并返回单个动作以在额外的减速器中“监听”。
    • 很好的答案 - 绝对解决了问题。无论如何-我在 redux-toolkit 文档中找到了一些可能也是解决方案的东西:redux-toolkit.js.org/api/matching-utilities#isrejectedwithvalue 但我没有把它缠住......(我的行动是带有rejectWithValue回调的异步thunk)
    • 抱歉,我不小心删除了我的评论(想要编辑) - 是的,我的操作是带有 rejectWithValue 回调的异步 thunk
    • @PixAff 不幸的是,我对 redux-toolkit 世界中的 redux-thunk 环境不太熟悉。我大约一个月前才开始使用 redux-toolkit,但使用 redux-observables/rxjs 进行异步操作。我已经用我认为应该有效(或接近目标)的方法更新了我的答案(还有时间让我复习更多文档)。
    • 查看builder.addMatcher 上的文档。你可以做(action) =&gt; action.type.endsWith('/rejected')
    【解决方案2】:

    您当前正在使用"Map Object" notation 定义您的extraReducers。你想改用"Builder Callback" notation

    通过构建器回调,您可以使用.addCase() 匹配单个操作,但您也可以使用.addMatcher() 处理多个操作。 addMatcher() 的第一个参数是一个函数,它接受 action 并返回 boolean 是否匹配。这里我们要匹配所有以'/rejected'结尾的动作。

    const errorsSlice = createSlice({
      name: "error",
      initialState,
      reducers: {
        clearError: (state) => {
          state.errors = null;
          state.isOpen = false;
        }
      },
      extraReducers: (builder) =>
        builder.addMatcher(
          // matcher function
          (action) => action.type.endsWith("/rejected"),
          // case reducer
          (state, { payload }) => {
            const errorArray = Object.values(payload.message).map(
              (key) => key.message
            );
            state.errors = errorArray;
            state.isOpen = true;
          }
        )
    });
    

    【讨论】:

    • 谢谢!你会说德鲁斯的答案有什么缺点吗?
    • 好的,我自己发现了 2 个缺点:您需要(稍微)更多代码,并且需要导入操作。
    【解决方案3】:

    你可以使用我的助手:

    import { AnyAction, AsyncThunk } from '@reduxjs/toolkit';
    
    enum AsyncActionStatusesEnum {
      PENDING = 'pending',
      FULFILLED = 'fulfilled',
      REJECTED = 'rejected',
    }
    
    //eslint-disable-next-line
    type GenericAsyncThunk = AsyncThunk<any, any, any>;
    type PendingAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.PENDING]>;
    type FulfilledAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.FULFILLED]>;
    type RejectedAction = ReturnType<GenericAsyncThunk[AsyncActionStatusesEnum.REJECTED]>;
    
    // gets a list of asynchronous actions and checks them for the status of at least one === 'pending'
    export function isSomeAsyncActionsPending(matchedActionTypes: GenericAsyncThunk[]) {
      return (action: AnyAction): action is PendingAction =>
        matchedActionTypes
          .map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.PENDING}`)
          .some(actionType => action.type.endsWith(actionType));
    }
    
    // gets a list of asynchronous actions and checks them for the status of at least one === 'fulfilled'
    export function isSomeAsyncActionsFulfilled(matchedActionTypes: GenericAsyncThunk[]) {
      return (action: AnyAction): action is FulfilledAction =>
        matchedActionTypes
          .map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.FULFILLED}`)
          .some(actionType => action.type.endsWith(actionType));
    }
    
    // gets a list of asynchronous actions and checks them for the status of at least one === 'rejected'
    export function isSomeAsyncActionsRejected(matchedActionTypes: GenericAsyncThunk[]) {
      return (action: AnyAction): action is RejectedAction =>
        matchedActionTypes
          .map(actionType => `${actionType.typePrefix}/${AsyncActionStatusesEnum.REJECTED}`)
          .some(actionType => action.type.endsWith(actionType));
    }
    

    使用示例:

    const fetchCoockBookList = createAsyncThunk(
      'dataSlice/fetchCookBookList',
      async (data: CoockBookParamsType) => await getCoockBook(data)
    );
    
    const fetchSeriesList = createAsyncThunk(
      'dataSlice/fetchSeriesList',
      async (data: SeriesParamsType) => await getSeries(data)
    );
    
    const isActionsPending = isSomeAsyncActionsPending([
        fetchDataList,
        fetchSeriesList,
    ]);
    
    
    const dataSlice = createSlice({
        // name, reducers, initialState, ...
    
        extraReducers: builder => {
        // others builder addCases, addMatchCases, ...
    
        builder
            .addMatcher(isActionsPending, state => {
                state.error = null;
                state.isLoading = true;
              })
            // addMatchers for fullfield, rejected
        }
    })
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-12-11
      • 1970-01-01
      • 2014-11-19
      • 2018-05-02
      • 2016-06-30
      • 2010-12-25
      • 2013-10-03
      • 2016-12-19
      相关资源
      最近更新 更多