2016-06-10 61 views
0

减速机对动作的新手问题。 From redux documentation减速机vs动作

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

由于相同的参数,减速机应计算下一个状态和 返回。没有惊喜。无副作用。没有API调用。没有突变。 只是一个计算。

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

  1. 用户可以将地图上的点,并得到这些点之间的路线。
  2. 当用户第一次点击地图时,这是他的出发点。当他第二次点击 - 这是他的终点。随后的点击会在前一个点和结束位置之间添加点。
  3. 每个点后,加入(除了第一个)必须新点和前一个点之间计算出的路线。所以如果我有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)) 
      }) 
     } 
     } 
    } 

对我来说,这在某种程度上违背到“但不指定应用程序的状态如何改变” - 因为从行动我指定插入我的新点的位置。
我可以计算减速器中的插入位置,但是如何获取完成点的路径数据?
这里的正确方法是什么?

回答

1

假设我们有一个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创建一个动作并发送它,到目前为止这么好,但这是一件容易的事情。

我争取的结构是在我的减速器的路线列表太多,所以让我们来扩展它来支持:

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功能是不是一个严重的假设,但它不是以实现优化的方式这个功能超硬任务(实际上意味着向服务器发送唯一途径,我们之前没有计算等)。 但是这只是逻辑和而不是应用程序的状态,因此,只要您保留由商店和缩减器定义的“合同”,您就可以随意以任何方式实现它。

希望这可以帮助你。

+0

所以看看你提出的'addPointAndUpdateRoutes =(point,index)'和'addPoint(point,index)'签名 - 索引计算是否被移动到了ui?但是如果我可以通过点击手动输入坐标的地图来添加一个点呢?关于'calculateRoutes'如果我有100分呢?或200?每次遍历整个列表,检查一些缓存(又一个状态......),并且用户是否可以始终从头开始?那么我必须使该缓存失效。 – gerasalus

+0

我猜你可以在UI中计算'index',我的意思是在两个已有点之间添加一个点可以在UI中非常清晰地表达出来。关于'calculateRoutes' - 你需要一个memoized函数,这意味着如果它已经计算出一个路由,它会立即返回它,这个实现需要在你的客户端和你的服务器上。如果用户有100或1000或甚至5000点,那么它并不重要,只要没有真正计算,它会发生得非常快。无论如何,如果你支持100分,你将不得不计算它们,不管UI的实现。 –

+0

以及我的观点是,我可能有两个,四个或五个不同的地方,我可以添加一个点。逻辑保持不变 - 如果第一个点 - 添加,第二个 - 添加它,如果第三个 - 在最后一个之前添加等等。所以这个逻辑不应该在每个UI组件中被复制。如果我使用聪明的calculateRoutes方法,这可以在reducer中完成(但是是吗?)。或者为什么我应该分开存放路线 - 他们不能没有一点生活。如果无法达到某个点,则可能没有路线,从而导致“终点”也可能无法到达的副作用。 – gerasalus