redux reducer vs action

时间:2016-06-10 10:49:47

标签: redux redux-thunk

关于减速器与动作的新手问题。 From redux documentation

  

动作描述了事情发生的事实,但没有指定   应用程序的状态如何随响应而变化。

  

给定相同的参数,reducer应该计算下一个状态和   把它返还。没有惊喜。无副作用。没有API调用。没有突变。   只是一个计算。

因此,如果我们考虑以下情况:

  1. 用户可以在地图上放置点,并在这些点之间获取路线。
  2. 当用户第一次点击地图时,这是他的出发点。当他第二次点击时 - 这是他的终点。随后的点击会在上一个点和结束位置之间添加点。
  3. 添加每个点后(第一个点除外)必须在新点和上一点之间计算路径。所以,如果我有S - > A - > F和添加点B(S→A - > B - > F)必须计算两个路径A - > B和B - > ˚F
  4. 所以我们在添加任何3+点时会产生两个副作用:

    • 新点不在列表的末尾
    • 必须将新路线计算为完成点。

    如果我将我的Point结构建模为:

     // typescript
    interface Point {
      coordinates;
      routeTo?;
    }
    

    我在动作中执行项目位置计算和路径检索是否正确,例如:

      // pseudo code
        export function addMapPoint(newPoint) {
          return (dispatch, getState) => {
            const {points} = getState();
            const position = getInsertPosition(points, newPoint)
            dispatch(addPoint(newPoint, position));
            if (points.length >= 2) {
              const previousPoint = getPreviousPoint(points, position);
              calculateRoute(newPoint, previousPoint).then(route => {
                dispatch(updateRoute(newPoint, route))
              })
            }
          }
        }
    

    对我来说,这与"有些矛盾,但没有说明应用程序的状态如何变化" - 因为从动作中我指定了插入新点的位置 我可以在reducer中计算插入位置,但是我如何获取Finish点的路径数据?
    这里的正确方法是什么?

1 个答案:

答案 0 :(得分:2)

假设我们有一个calculateRoute函数接受两个点,并返回一个解决它们之间路由的promise。

首先,让我们创建一个简单的动作创建器,以便我们知道我们的点被正确存储:

let addPoint = (point, index) => {
    return {
        type: 'ADD_POINT',
        point: point,
        index: index
    }
}

然后,让我们在reducer中处理这个动作:

let reducer = (state = { points: [] }, action) => {
    switch (action.type) {
        case 'ADD_POINT':
            return Object.assign({}, state, {
                points: [
                    ...state.points.slide(0, action.index),
                    action.point,
                    ...state.points.slide(action.index + 1)
                ]
            });
        default:
            return state;
    }
}

现在,在用户添加一个点后,我们使用addPoint创建一个动作并发送它,到目前为止一切都很好,但这很容易。

我努力的结构是在我的reducer中也有一个路由列表,所以让我们扩展它以支持:

let reducer = (state = { points: [], routes: [] }, action) => {
    switch (action.type) {
        case 'ADD_POINT':
            return Object.assign({}, state, {
                points: [
                    ...state.points.slide(0, action.index),
                    action.point,
                    ...state.points.slide(action.index + 1)
                ]
            });
        case 'UPDATE_ROUTES':
            return Object.assign({}, state, {
                routes: action.routes
            });
        default:
            return state;
    }
}

动作创建者将是:

let updateRoutes = (routes) => {
    return {
        type: 'UPDATE_ROUTES',
        routes: routes
    }
}

请注意我们正在覆盖整个路线集合。现在没关系,但可能在生产系统中你想稍微优化一下。

现在我们实际上需要编写一些逻辑。我将假设一个方便的假设,即我们有一个calculateRoutes得到一个点集合,并返回一个解析相应路由列表的promise,每个路由将是一个对象,包含两个点和实际路由。话虽如此,我们的thunk现在看起来像这样:

addPointAndUpdateRoutes = (point, index) => {
    return (dispatch, getState) => {
        // First, update the point list
        dispatch(addPoint(point, index));

        // Now, recalculate routes
        calculateRoutes(getState().points)
            .then(routes => dispatch(updateRoutes(routes));
    };
};

在我看来哪种方式更好。

现在,当然,假设我们有一个神奇的calculateRoutes函数并不是一个严肃的假设,尽管以优化的方式实现这个函数并不是一个超级难的任务(意味着实际上只向我们发送服务器的路径)之前没有计算,等等)。 但是这只是逻辑和 NOT 应用程序的状态,因此,只要您保留由商店和减少者定义的“合同”,您就可以自由实施你喜欢的任何方式。

希望这会对你有所帮助。