【问题标题】:Reducer behavior减速机行为
【发布时间】:2018-02-03 23:59:12
【问题描述】:

据我了解,当一个动作被调用时,所有减速器都会响应。如果 reducer 的 switch case 语句中存在 action,则执行。如果没有,则执行 case: default 以保留现有状态。

当 action 存在于 reducer 但它试图更新的特定属性不存在时,它的行为似乎正常,因为没有要更新的内容。

例如,我有一个动作创建器,用于设置我的模态框的visible 属性。每个模态都有自己的Id。我的代码如下所示:

export default (state = initialState, action) => {
   case types.SET_MODAL_IS_VISIBLE:
      return Object.assign({}, state,
         { modal22: action.value }
      )}

我在多个reducer 中有SET_MODAL_IS_VISIBLE,但如果modal22 未在特定reducer 中定义,则不会发生任何错误。

现在,我有一个引发错误的场景。我有一个我构建的通用日期选择器组件,它可以用作单个独立的日期选择器,或者它可以“链接到”另一个。如果我需要用户给我两个日期,例如,第二种情况很有用。开始和结束日期。

我还构建了一个功能,如果日期选择器与另一个日期选择器结合使用,当用户在第一个日期选择器中设置日期时,我会在第二个日期选择器中禁用该日期之前的所有日期,因为我没有希望用户无意中选择了早于开始日期的结束日期。

我将我的日期选择器定义如下:

const initialState = {
    datePickers: {
        "startDatePicker": {
            activeDate: "8/25/2017",
            disabledBefore: "",
            linkedTo: "endDatePicker"
         },
         "endDatePicker": {
            activeDate: "",
            disabledBefore: "8/25/2017" // This date is set when the user sets the active date in startDatePicker
            linkedTo: ""
       }
    }
}

这个场景有点有趣,因为我的 reducer 中一个属性的状态更改会触发另一个属性的状态更改。这并不难做到,而且我有办法控制何时进行更新。

设置禁用日期的操作如下所示:

...
case types.SET_DISABLED_DATES:
    return Object.assign({}, state,
       datePickers: Object.assign({}, state.datePickers, {
          datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
             disabledBefore: action.value
          })
    })

请记住,即使日期选择器被用作独立的,我也可以并且应该能够设置disabledBefore。所以,我在每个减速器中都需要我的SET_DISABLED_DATES

我遇到的问题是,每当我调用 SET_DISABLED_DATES 时,我在减速器中遇到错误,其中日期选择器被用作单个/独立的,因为它的对的日期选择器 ID 未在减速器中定义.

例如,在projectsReducer 中,我可以使用日期选择器作为配对的一部分,这样startDatePickerendDatePicker 都被定义并且一切正常。

但我可能在tasksReducer 中使用了单个实例日期选择器,它也响应SET_DISABLED_DATES 调用,但它失败了,因为它找不到endDatePicker。在这种情况下,tasksReducer 正在响应我在projectsReducer 中设置endDatePickerdisabledDates 属性的调用。

我已经发布了两个关于这个的问题,我在这里看到的唯一真正的解决方案是我需要在我的减速器中有一个看起来像这样的条件:

...
case types.SET_DISABLED_DATES:
   if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
      return Object.assign({}, state,
       datePickers: Object.assign({}, state.datePickers, {
          datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
             disabledBefore: action.value
          })
    })
   } else {
      return state;
   }

诚然,这看起来有点像拼凑,但我真的想不出另一个解决方案。

同样,问题在于,只要所有 reducer 都响应 SET_DISABLED_DATES,就可以保证特定日期选择器不会出现,Object.assign() 会抛出错误。

有什么建议吗?减速器中的简单条件是要走的路吗?是杂牌吗?

附:我尝试了这段代码,它工作正常并解决了问题。一方面,我觉得这有点反模式,但另一方面,在尝试更新之前确保我想在 reducer 中更新的属性存在似乎是个好主意。感谢您对此的反馈。谢谢。

【问题讨论】:

    标签: reactjs redux react-redux


    【解决方案1】:

    您只是在设置状态之前在 reducer 中进行基本验证。那很好。我认为在动作创建器中检查存储以防止对不在存储中的对象调度动作不是一个好习惯(无论如何你会怎么做!)。

    我不明白的是,如何将日期选择器链接到不在商店中的另一个日期选择器?也许在组件的didMountwillUnmount 上调度创建和拆除操作?

    我不知道您的全部要求,但我认为我们可以让它变得更简单。我会做这样的事情:

    商店:

    {
      datePickers: {
        id1: {
          value: '',
          minValue: '',
          maxValue: '',
        },
        id2: {
          value: '',
          minValue: '',
          maxValue: '',
        }
      }
    }
    

    现在,除非您正在制作某种始终成对运行的耦合日期选择器组件,否则我认为最简洁的方法是在父组件的mapDispactchToProps 函数中的链接日期选择器中设置禁用值。

    您可以在此处将ids 设置为组件,并且您确切知道应该先禁用哪个组件。

    类似:

    dispatch => ({
      setArrivalDate(value) {
        dispatch(datePickerActions.setValue(arrivalDateId, value);
        dispatch(datePickerActions.setMaxValue(depatureDateId, value);
      },
      setDepatureDate(value) {
        dispatch(datePickerActions.setValue(depatureDateId, value);
        dispatch(datePickerActions.setMinValue(arrivalDateId, value);
      }
    })
    

    这可能不够抽象,但很干净。

    如果您有一个配对的组件,您可以做同样的事情,但您仍然需要知道哪个日期在另一个之前。围绕它进行通用抽象会很麻烦。

    【讨论】:

    • 不是 100% 清楚您所说的“在 liniked 日期选择器中设置禁用值 是什么意思,但如果我理解正确的话,这就是我正在做的事情。每个日期选择器必须包含属于它的数据。禁用日期属于endDatePicker,因此它们存储在那里。这里的关键是在startDatePicker中设置日期导致在endDatePicker中设置禁用日期。我很好地控制了这一点在我的处理函数中。我只是检查startDatePickerlinkedTo 属性中是否有值。如果是这样,我调用我的动作创建者在endDatePicker 中设置禁用日期。
    • 每个日期选择器在减速器中都有自己独特的Id。如果您查看我的代码,datePickers 对象包含在该特定减速器中定义的所有日期选择器。 startDatePickerendDatePickerIds。那里甚至可能有更多的日期选择器。您正在苦苦挣扎的部分是:假设我在帖子中输入的代码是针对reducer1 的,其中startDatePickerendDatePicker 是配对的。在reducer2 中,我可能有未配对的reservationDatePicker。我仍然在reducer2 中有SET_DISABLED_DATES,所以它会响应并给出错误。
    • 它正在寻找 endDatePicker 进行更新,因为这是 action.datePickerId 中的内容,但 endDatePicker 未在 reducer2 中定义。 reducer2 中定义的唯一日期选择器是 reservationDatePicker。所以出现了错误。
    • 如果我只是在做datePickers[action.datePickerId]: action.value,那就没问题了。 Reducers 一直在处理这个问题而不会抛出错误。但是我只需要为endDatePicker 更新对象的一部分,所以我正在做一个datePickers[action.datePickerId]: Object.assign({}, datePickers[action.datePickerId, {// update value here}),reducer 会抛出一个错误,因为datePickers["endDatePicker"] 没有在`reducer2' 中定义。
    【解决方案2】:

    删除下面代码中的粗体部分

        ...
        case types.SET_DISABLED_DATES:
           if(typeof state.datePickers[action.datePickerId] !== "undefined") { // Making sure that what I'm trying to update exists in the reducer
              return Object.assign({}, state,
               datePickers: Object.assign({}, state.datePickers, {
                  datePickers[action.datePickerId]: Object.assign({}, state.datePickers[action.datePickerId], {
                     disabledBefore: action.value
                  })
            })
           } else {
              return state;
           }

    此外,一点点 es6 传播和辅助 switchcase 函数使这段代码更具可读性。

    const newReducer = (state = defaultState, action) => switchcase({
      [types.SET_DISABLED_DATES]:
        state.datePickers[action.datePickerId] === undefined
          ? state
          : ({ ...state,
            datePickers: { ...state.datePickers,
              [action.datePickerId]: { ...state.datePickers[action.datePickerId],
                disabledBefore: action.value,
              },
            },
          }),
    })(state)(action.type);
    

    使用lodash/fp/set,代码变为

    const reducerWithLodash = (state = defaultState, action) => 
    switchcase({
      [types.SET_DISABLED_DATES]:
        state.datePickers[action.datePickerId] === undefined
          ? state
          : set({...state}, `datePickers.${action.datePickerId}.disabledBefore`, action.value)
    })(state)(action.type)
    

    我还没有测试过 lodash 版本,所以请对它持保留态度(Dan Abramov 似乎是 approve

    【讨论】:

    • 您仍在进行基本检查,以确保我要更新的属性在减速器中,这是我的问题。所以我认为你只是提供了一些可读性改进。
    • 啊我现在明白了。如果路径不存在,lodash/fp/set 创建路径。也许您可以修改它的实现以忽略不存在的路径。
    • 我最近一直在使用 redux,我发现如果你的状态形状是静态的并且你的渲染只基于状态内容,而不是改变形状,它会更好。例如,您可以始终在其中放置一个空对象和另一个指示是否呈现该对象的布尔属性,而不是基于属性是否存在进行渲染。
    猜你喜欢
    • 2019-10-07
    • 2022-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多