2

关于减速器与动作的新手问题。来自 redux 文档

动作描述了某事发生的事实,但不指定应用程序的状态如何变化作为响应。

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

因此,如果我们考虑以下场景:

  1. 用户可以在地图上放置点并获取这些点之间的路线。
  2. 当用户第一次点击地图时,这是他的起点。当他第二次点击时——这就是他的终点。随后的单击在前一个点和结束位置之间添加点。
  3. 添加每个点后(第一个除外),必须计算新点和前一个点之间的路线。因此,如果我有 S -> A -> F 并添加点 B (S -> A -> B -> F) 必须计算两条路线 A -> B 和 B -> F

所以我们在添加任何 3+ 点时会有两个副作用:

  • 新点不在列表末尾
  • 新路线必须计算到终点。

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

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

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

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

对我来说,这在某种程度上与“但不指定应用程序的状态如何变化”相矛盾——因为从动作中我指定了在哪里插入我的新点。
我可以计算减速器中的插入位置,但是如何获取终点的路线数据?
这里的正确方法是什么?

4

1 回答 1

5

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

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

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并调度它,到目前为止一切顺利,但这是简单的事情。

我争取的结构是在我的减速器中也有一个路由列表,所以让我们扩展它以支持它:

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获取点的集合,并返回一个解析相应路线列表的承诺,每条路线都是一个对象,包含两个点和实际路线。话虽如此,我们的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函数并不是一个严肃的假设,尽管以优化的方式实现这个函数并不是一项超级艰巨的任务(这意味着实际上只向服务器发送我们之前没有计算过的路由等)。但这只是逻辑,而不是应用程序的状态,因此,只要您保留 store 和 reducer 定义的“合同”,您就可以自由地以任何您喜欢的方式实现它。

希望这对您有所帮助。

于 2016-06-10T12:20:28.500 回答