redux减速器与动作

redux减速器与动作,redux,redux-thunk,Redux,Redux Thunk,关于减速机与行动的新手问题: 动作描述发生了某事的事实,但没有具体说明 应用程序的状态如何随响应而变化 及 给定相同的参数,reducer应该计算下一个状态和 归还它。没有意外。没有副作用。没有API调用。没有突变。 只是计算一下 所以,如果我们考虑下面的场景: 用户可以在地图上放置点,并获得这些点之间的路线 当用户第一次单击地图时,这是他的起点。当他第二次点击时——这是他的终点。后续单击可在上一点和终点位置之间添加点 添加每个点后(第一个点除外),必须计算新点和上一个点之间的路线。因此,如果我

关于减速机与行动的新手问题:

动作描述发生了某事的事实,但没有具体说明 应用程序的状态如何随响应而变化

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

所以,如果我们考虑下面的场景:

  • 用户可以在地图上放置点,并获得这些点之间的路线
  • 当用户第一次单击地图时,这是他的起点。当他第二次点击时——这是他的终点。后续单击可在上一点和终点位置之间添加点
  • 添加每个点后(第一个点除外),必须计算新点和上一个点之间的路线。因此,如果我有S->A->F并添加点B(S->A->B->F),则必须计算两条路线A->B和B->F
  • 因此,在添加任何3+点时,我们有两种副作用:

    • 新点不会放置在列表的末尾
    • 新路线必须计算到终点
    如果我将我的点结构建模为:

     // 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))
              })
            }
          }
        }
    
    对我来说,这与“但不要指定应用程序的状态如何更改”相矛盾,因为从操作开始,我指定在何处插入我的新点。
    我可以计算减速器中的插入位置,但如何获取终点的路线数据?

    这里的正确方法是什么?

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

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

    let addPoint = (point, index) => {
        return {
            type: 'ADD_POINT',
            point: point,
            index: index
        }
    }
    
    然后,让我们在减速器中处理此操作:

    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中也有一个routes列表,因此让我们扩展它以支持:

    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
        }
    }
    
    请注意,我们正在覆盖整个routes集合。目前还可以,但在生产系统中,您可能需要对其进行一些优化

    现在我们需要写一些逻辑。我将假设一个方便的假设,我们有一个
    计算器outes
    ,它获取点的集合,并返回一个承诺,该承诺解析相应路线的列表,每个路线将是一个对象,包含两个点和实际路线。话虽如此,我们的
    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));
        };
    };
    
    在我看来,这是更好的方式


    当然,假设我们有一个神奇的
    calculatoroutes
    函数并不是一个严肃的假设,尽管以优化的方式实现这个函数并不是一项非常困难的任务(这意味着实际上只向服务器发送我们以前没有计算过的路由,等等)这只是逻辑,而不是应用程序的状态,因此,只要您保持存储和还原器定义的“契约”,您就可以自由地以任何方式实现它


    希望这对您有所帮助。

    那么看看您建议的
    addPointAndUpdateRoutes=(点,索引)
    addPoint(点,索引)
    签名-索引计算是否移动到用户界面?但是如果我可以通过点击地图手动输入坐标来添加一个点呢?。关于
    calculateRoutes
    如果我得了100分怎么办?还是200?每次在整个列表中循环,检查一些缓存(还有另一个状态…)以及用户是否总是可以从头开始?然后我必须使缓存失效..我想你可以在UI中计算
    索引
    ,我的意思是在两个现有点之间添加一个点可以在UI中非常清晰地表达出来。关于
    calculateRoutes
    -您需要有一个记忆功能,这意味着如果它已经计算了路由,它会立即返回路由,此实现需要在您的客户端和服务器上实现。如果一个用户有100分、1000分甚至5000分,这并不重要,只要不是真正计算出来的,它就会发生得很快。无论如何,如果你支持100个点,你就必须计算它们,不管UI实现如何。我的观点是,我可能有两个、四个或五个不同的地方可以添加点。并且逻辑保持不变——如果第一个点添加,第二个点添加,如果第三个点添加在最后一个点之前,等等。所以这个逻辑不应该在每个UI组件中重复。如果我使用智能计算器的方法,这可以在减速机中完成(但是否正确?)。或者我为什么要分开储存路线——它们不能没有一个点。如果无法到达某个点,则该点可能没有路线,这会产生副作用,即终点也可能无法到达。可以在五个不同的位置添加一个点,但所有这些位置都应引用相同的逻辑,不能在减速器中<代码>计算器输出与减速机也没有任何关系,只是减速机存储其输出。点是一个实体,路线是一个完全不同的实体,正好包含两个点。