通量存储或动作(或两者)是否应该涉及外部服务?


122

商店应该保持自己的状态并能够调用网络和数据存储服务……在这种情况下,这些动作只是愚蠢的消息传递者,

-要么-

...商店应该是动作中不可变数据的愚蠢接收者(而动作是在外部源之间获取/发送数据的动作吗?在这种情况下,商店将充当视图模型,并且能够聚合/过滤它们的数据根据动作所馈送的不可变数据设置自己的状态之前。

在我看来,它应该是一个或另一个(而不是两者的结合)。如果是这样,为什么一个偏爱/推荐另一个偏爱?


2
这篇文章可能会帮助code-experience.com/…–
Markus-ipse

对于那些评估通量模式的各种实现的人,我强烈建议您看一下Redux github.com/rackt/redux。存储是作为纯函数实现的,它们采用当前状态并发出该状态的新版本。由于它们是纯函数,因此它们是否可以调用网络和存储服务的问题就从您手中掌握了:它们不能。
plaxdan 2015年

Answers:


151

我已经看到两种方式都实现了通量模式,并且我自己都做完之后(最初采用了前一种方法),我认为存储应该是动作中数据的愚蠢接收者,并且写操作的异步处理应该存在于动作创作者。(异步读取的处理方式可能有所不同。)以我的经验,按重要性顺序,它有一些好处:

  1. 您的商店将完全同步。这使您的商店逻辑更易于遵循和测试,非常容易-只需使用某个给定状态实例化商店,向其发送操作,然后检查状态是否按预期更改。此外,流量中的核心概念之一是防止级联调度并同时防止多个调度。当商店进行异步处理时,这很难做到。

  2. 所有动作分派均由动作创建者执行。如果您在商店中处理异步操作,并且希望使商店的动作处理程序保持同步(并且应该为了获得通量单次派发保证),则商店将需要触发其他SUCCESS和FAIL动作以响应异步处理。相反,将这些调度放到动作创建者中有助于将动作创建者和商店的工作分开;此外,您不必深入研究商店逻辑即可确定从何处调度操作。在这种情况下,典型的异步操作可能看起来像这样(dispatch根据您使用的助焊剂的风味更改调用的语法):

    someActionCreator: function(userId) {
      // Dispatch an action now so that stores that want
      // to optimistically update their state can do so.
      dispatch("SOME_ACTION", {userId: userId});
    
      // This example uses promises, but you can use Node-style
      // callbacks or whatever you want for error handling.
      SomeDataAccessLayer.doSomething(userId)
      .then(function(newData) {
        // Stores that optimistically updated may not do anything
        // with a "SUCCESS" action, but you might e.g. stop showing
        // a loading indicator, etc.
        dispatch("SOME_ACTION_SUCCESS", {userId: userId, newData: newData});
      }, function(error) {
        // Stores can roll back by watching for the error case.
        dispatch("SOME_ACTION_FAIL", {userId: userId, error: error});
      });
    }

    否则可能会在各种动作之间重复的逻辑应提取到单独的模块中;在此示例中,该模块将是SomeDataAccessLayer,该模块处理实际的Ajax请求。

  3. 您需要较少的动作创建者。这没什么大不了的,但是很高兴。如#2所述,如果您的商店具有同步操作分派处理(应该这样做),则需要触发额外的操作来处理异步操作的结果。在动作创建者中进行分派意味着单个动作创建者可以通过处理异步数据访问本身的结果来分派所有三种动作类型。


15
我认为发起网络api调用(操作创建者与商店)的源代码不如成功/错误回调应创建操作的事实重要。因此,数据流始终是:操作->调度程序->存储->视图。
fisherwebdev

1
将实际的请求逻辑放在API模块中会更好/更容易测试吗?因此,您的API模块可以仅返回您从中调度的承诺。动作创建者在发出初始“待定”动作后,仅根据解决/失败进行分派。剩下的问题是组件如何侦听这些“事件”,因为我不确定请求状态应映射到存储状态。
2015年

@backdesk这正是我在上面的示例中所做的:调度一个初始的未决操作("SOME_ACTION"),使用API​​发出一个SomeDataAccessLayer.doSomething(userId)返回诺言的请求(),并在这两个.then函数中调度其他操作。如果应用程序需要知道状态的状态,则请求状态可以(或多或少)映射到存储状态。此地图的方式取决于应用程序(例如,每个评论都有一个单独的错误状态,一个la脸书,或者可能有一个全局错误组件)
Michelle Tilley 2015年

@MichelleTilley“流量中的核心概念之一是防止级联调度,并一次阻止多个调度;这在您的商店执行异步处理时很难做到。” 这对我来说是关键。说得好。

51

我在Facebook上向开发人员发布了这个问题,我从比尔·费舍尔那里得到的答案是:

当响应用户与UI的交互时,我将在动作创建者方法中进行异步调用。

但是,如果您有股票报价器或其他非人工驾驶员,则从商店拨打电话会更好。

重要的是在错误/成功回调中创建操作,因此数据始终源自操作


尽管这很有意义,但为什么a call from store works better when action triggers from non-human driver 呢?
SharpCoder

@SharpCoder我想如果您有实时报价器或类似的东西,您实际上并不需要触发操作,而从存储库中执行操作时,您可能需要编写更少的代码,因为存储库可以立即访问状态并发出变化。
Florian Wendelborn

8

商店应该做所有事情,包括获取数据,并向组件发出信号,通知商店的数据已更新。为什么?因为动作可以是轻量级的,一次性的和可替换的,而不影响重要的行为。所有重要的行为和功能都在商店中发生。这也防止了行为的重复,否则这些行为将以两个非常相似但不同的动作来复制。商店是您(处理)事实的唯一来源。

在每个Flux实现中,我都看到Actions基本上是将事件字符串转换为对象,就像传统上一样,您有一个名为“ anchor:clicked”的事件,但是在Flux中,它将定义为AnchorActions.Clicked。它们甚至太“笨拙”,以致于大多数实现都有单独的Dispatcher对象,以将事件实际分派到正在监听的商店。

我个人很喜欢Reflux的Flux实现,其中没有单独的Dispatcher对象,而Action对象自己进行调度。


编辑:Facebook的Flux实际上会获取“动作创建者”,因此他们确实使用了智能动作。他们还使用存储来准备有效负载:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/ChatMessageActionCreator.js#L27(第27和28行)

然后,完成时的回调将这次以提取的数据作为有效负载触发新动作:

https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/ChatWebAPIUtils.js#L51

因此,我认为这是更好的解决方案。


这是什么Reflux实施?我还没听说 您的答案很有趣。您的意思是您的商店实现应具有执行API调用的逻辑,等等?我认为商店应该只接收数据并更新其值。他们根据特定操作进行过滤,并更新其商店的某些属性。
杰里米D

Reflux与Facebook的Flux略有不同:github.com/spoike/refluxjs商店管理应用程序的整个“模型”域,而Actions / Dispatchers只能将事物缝合并粘合在一起。
Rygu 2014年

1
所以我一直在考虑这个问题,并且(几乎)回答了我自己的问题。我会在这里将其添加为答案(供其他人投票),但显然我在stackoverflow上太缺乏业力了,无法发布答案。因此,这里有一个链接:groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
plaxdan 2014年

感谢您的google group链接,它看起来确实很有用。我也更喜欢通过调度程序进行的所有操作,以及商店中非常简单的逻辑,基本上就是更新所有数据。@Rygu我将检查反流。
杰里米D

我用另一种观点编辑了答案。似乎两种解决方案都是可能的。我几乎肯定会选择Facebook在其他方面的解决方案。
Rygu 2014年

3

我将提供一个支持“哑”动作的论点。

通过将收集视图数据的责任置于操作中,您可以将操作与视图的数据需求结合起来。

相反,以声明方式描述用户意图或应用程序中的某些状态转换的通用动作,允许任何响应该动作的商店将其转换为专门针对所预订视图的状态。

这使其适合更多但更小,更专业的商店。我主张这种风格,因为

  • 这使您在视图如何使用存储数据方面更具灵活性
  • 与“智能”操作相比,专门用于使用它们的视图的“智能”商店将比“智能”操作更小,耦合度也更低,而“智能”操作可能会依赖许多视图

存储的目的是向视图提供数据。“动作”这个名称向我暗示,其目的是描述我的应用程序中的更改。

假设您必须将一个小部件添加到现有的“仪表板”视图中,该视图显示您的后端团队刚刚推出的一些新的汇总数据。

使用“智能”操作,您可能需要更改“刷新仪表板”操作,以使用新的API。但是,抽象意义上的“刷新仪表板”没有改变。视图的数据要求已发生变化。

使用“哑”动作,您可以添加一个新的商店以供新的小部件使用,并进行设置,以便在收到“刷新仪表板”动作类型时发送对新数据的请求,并将其公开给新的小部件准备就绪后。对我来说,当视图层需要更多或不同的数据时,我所更改的就是这些数据的来源:存储。


2

gaeron的flux-react-router-demo具有“正确”方法的一个很好的实用程序变体。

一个ActionCreator从一个外部API服务生成一个Promise,然后将Promise和三个动作常量传递dispatchAsync给代理/扩展Dispatcher中的一个函数。dispatchAsync将始终调度第一个操作,例如“ GET_EXTERNAL_DATA”,并且一旦诺言返回,它将调度“ GET_EXTERNAL_DATA_SUCCESS”或“ GET_EXTERNAL_DATA_ERROR”。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.