【问题标题】:How to compose redux reducers with dependent state如何组合具有依赖状态的 redux reducer
【发布时间】:2015-11-27 18:05:15
【问题描述】:

我正在开发一个允许将“小部件”添加到页面并在 2D 空间中操作的 React/Redux 应用程序。要求可以一次选择和操作多个小部件。我当前状态树的简化版本如下所示...

{
  widgets: {
    widget_1: { x: 100, y: 200 },
    widget_2: { x: 300, y: 400 },
    widget_3: { x: 500, y: 600 }
  },
  selection: {
    widgets: [ "widget_1", "widget_3" ]
  }
}

我目前有这棵树由 2 个减速器管理,一个管理 widgets 状态,另一个管理 selection 状态。选择状态reducer可以简化为(注意:我也在使用Immutable.js)...

currentlySelectedWidgets(state = EMPTY_SELECTION, action) {
  switch (action.type) {
    case SET_SELECTION:
      return state.set('widgets' , List(action.ids));
    default:
      return state
  }
}

这一切似乎都很好,但我现在有一个要求,我正在努力适应这个模型......

出于 UI 目的,我需要让当前选择有一个 x/y 属性,该属性反映当前选择的小部件 x/y 坐标的左上角。一个示例状态可能看起来像...

{
  widgets: {
    widget_1: { x: 100, y: 200 },
    widget_2: { x: 300, y: 400 },
    widget_3: { x: 500, y: 600 }
  },
  selection: {
    x: 100,
    y: 200,
    widgets: [ "widget_1", "widget_3" ]
  }
}

这里的逻辑相当简单,例如找到所有选定小部件xy 值的min(),但我不确定我应该如何处理减速器组合,因为currentlySelectedWidgets 减速器无法访问widgets 部分状态树。我考虑过……

  • 将减速器合并到一个减速器中:这似乎不是一个很好的可扩展解决方案。
  • 通过操作传递当前的小部件列表:这似乎特别讨厌。
  • 寻找更好的状态树建模方法:我有一个想法,但任何替代版本似乎都有其他缺点。
  • 构建一个自定义 combineReducers() 可以将小部件列表作为附加参数输入我的 currentlySelectedWidgets() 减速器:我认为这可能是我最好的选择。

我很想听听关于其他人如何管理类似情况的任何其他建议,似乎管理“当前选择”必须是其他人必须解决的常见状态问题。

【问题讨论】:

    标签: javascript reactjs redux


    【解决方案1】:

    这是Reselect 的一个很好的用例:

    创建一个选择器来访问您的状态并返回派生数据。

    举个例子:

    import { createSelector } from 'reselect'
    
    const widgetsSelector = state => state.widgets;
    const selectedWidgetsSelector = state => state.selection.widgets;
    
    function minCoordinateSelector(widgets, selected) {
      const x_list = selected.map((widget) => {
        return widgets[widget].x;
      });
    
      const y_list = selected.map((widget) => {
        return widgets[widget].y;
      });
    
      return {
        x: Math.min(...x_list),
        y: Math.min(...y_list)
      };
    }
    
    const coordinateSelector = createSelector(
      widgetsSelector,
      selectedWidgetsSelector,
      (widgets, selected) => {
        return minCoordinateSelector(widgets, selected);
      }
    );
    

    coordinateSelector 现在可以访问您的最小 xy 属性。上面的选择器只会在widgetsSelectorselectedWidgetsSelector 状态发生变化时更新,这使得这是一种非常高效的方式来访问您所追求的属性并避免在您的状态树中出现重复。

    【讨论】:

    • 在尝试实现 Hummlas 的基于 thunk 的方法和这种基于重新选择的方法后,我觉得这种方法更适合我的模型,并提供了一个更清洁、更易于维护的解决方案。我想我正在慢慢学习的一般经验法则是不要在状态树中存储任何可以从现有树中重新计算的东西。
    • 忘了提一下,这个示例答案中有一个小错误,最后的minSelector(widgets, selected) 应该是minCoordinateSelector(widgets, selected) 它不太可能有人会照原样复制这段代码,但以防万一。
    【解决方案2】:

    如果您使用 thunk 中间件 (https://github.com/gaearon/redux-thunk),您可以将 SET_SELECTION 操作设为 thunk,这将允许它在您的 reducer 接收调度之前读取整个状态。

    // action creator
    function setSelection(selectedWidgetId) {
        return (dispatch, getState) => {
            const {widgets} = this.getState();
            const coordinates = getSelectionCoordinates(widgets, selectedWidgetIds);
    
            dispatch({
                type: SET_SELECTION,
                payload: {
                    widgets: selectedWidgets,
                    x: coordinates.x,
                    y: coordinates.y
                }
            });
    }
    

    通过这种方式,您可以在选择缩减器中获得所需的所有信息,而无需将所有小部件对象的列表传递给您的操作。

    【讨论】:

    • 谢谢你,它似乎可以解决问题,我很想看看随着代码库的增长这种方法的扩展性如何,但我们会看到的。
    • 我今天不能再提出问题了,所以我这样问。如果@Ashley 想从currentlySelectedWidgets(state,action) reducer 中删除特定的小部件怎么办?如何做到这一点?
    【解决方案3】:

    我正在使用这个:

    CommonReducer.js

    export default function commonReducer(state = initialState.doesNotMatter, payload) {
       switch (payload.type) {
         case "JOINED_TYPE": {
           let newState = Object.assign({}, state);
           return newState;
         }
         default:
           return state;
      }
    }
    

    SomeOtherReducer.js

    import commonReducer from './commonReducer';
    
    export default function someReducer(state = initialState.somePart, payload) {
           switch (payload.type) {
             case "CUSTOM_TYPE": {
               let newState = Object.assign({}, state);
               return newState;
             }
             default:{
               return commonReducer(state, payload);
             }
          }
    }
    

    【讨论】:

      【解决方案4】:

      你可以尝试使用:

      redux-named-reducers

      这使您可以像这样在代码中的任何位置获取状态:

      const localState1 = getState(reducerA.state1)
      const localState2 = getState(reducerB.state2)
      

      或者像这样将外部依赖状态分配给您的减速器:

      reducerA.extState1 = reducerB.state1

      然后像这样访问它:

      getState(reduerA.extState1)

      【讨论】:

        猜你喜欢
        • 2018-11-14
        • 2019-05-07
        • 2021-08-04
        • 1970-01-01
        • 2019-02-03
        • 1970-01-01
        • 2023-03-22
        • 1970-01-01
        • 2023-03-23
        相关资源
        最近更新 更多