redux 减速器 vs 动作

ger*_*lus 2 redux redux-thunk

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

\n\n
\n

操作描述了发生了某些事情的事实,但不指定应用程序状态如何更改以响应。

\n
\n\n

\n\n
\n

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

\n
\n\n

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

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

因此,在添加任何 3 个以上的点时,我们会产生两个副作用:

\n\n
    \n
  • 新点未放置在列表末尾
  • \n
  • 必须计算到达终点的新路线。
  • \n
\n\n

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

\n\n
 // typescript\ninterface Point {\n  coordinates;\n  routeTo?;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

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

\n

Gil*_*tzi 5

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

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

let addPoint = (point, index) => {
    return {
        type: 'ADD_POINT',
        point: point,
        index: index
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,让我们在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,在用户添加一个点后,我们使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

动作创建者将是:

let updateRoutes = (routes) => {
    return {
        type: 'UPDATE_ROUTES',
        routes: routes
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们将覆盖整个路线集合。目前还可以,但在生产系统中您可能需要对其进行一些优化。

现在我们实际上需要编写一些逻辑。我将假设一个方便的假设,即我们有一个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));
    };
};
Run Code Online (Sandbox Code Playgroud)

我认为这更好。


当然,假设我们有一个神奇的calculateRoutes功能并不是一个严肃的假设,尽管以优化的方式实现这个功能并不是一个超级困难的任务(意味着实际上只向服务器发送我们之前没有计算过的路由等)。但这只是逻辑,而不是应用程序的状态,因此,只要您保留存储和减速器定义的“契约”,您就可以自由地以任何您喜欢的方式实现它。

希望这对您有帮助。