2015-11-27 15 views
12

我一个反应,和/终极版应用程序,它允许“小工具”的工作被添加到一个页面,在二维空间操纵。需要一次选择和操作多个小部件。我目前的状态树的简化版本,看起来像下面...如何撰写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状态管理。选择状态减速机可以简化为(注:我使用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" ] 
    } 
} 

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

  • 将reducer合并为一个reducer:这似乎不是一个很好的可扩展解决方案。
  • 传递部件的现有的清单,行动围绕:这似乎特别讨厌。
  • 找到一个更好的方式来状态树模型:我有一个看法,但任何替代版本似乎有其他缺点。
  • 构建定制combineReducers()可以养活小部件列表进入我的currentlySelectedWidgets()减速机作为一个额外的参数:我想这可能是我最好的选择。

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

回答

8

这是一个很好的用例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状态发生更改时,上述选择器才会更新,使其成为访问您之后的属性的非常高效的方式,并避免状态树中的重复。

+3

在尝试实施Hummlas的基于thunk的方法和基于重新选择的方法之后,我觉得这种方法更好地适应了我的模型,并提供了更清洁和更易维护的解决方案。我想我慢慢学习的一般经验法则是不在状态树中存储任何可以从现有树中重新计算的东西。 – andykent

+1

忘记了,在这个例子的答案中有一个小错误,最后一个'minSelector(widget,selected)'应该读为'minCoordinateSelector(widgets,selected)',不太可能任何人都会按原样复制这段代码,但只是为了以防万一他们是这样。 – andykent

4

如果使用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 
      } 
     }); 
} 

这样,你让你在选择减速所需要的所有信息,而不必沿所有控件对象到你的动作列表中通过。

+0

感谢这个,好像它会解决这个问题,我我很感兴趣,看看随着代码库的增长,这种方法的效果如何,但我们会看到。 – andykent

+0

今天我不能再打开任何问题,所以我问这个问题。 如果@Ashley想从'currentlySelectedWidgets(state,action)'reducer中删除一个特定的部件会怎么样?这个怎么做? – zatziky

0

我使用这个:

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); 
     } 
     } 
}