通量存储或操作(或两者)应该触及外部服务吗?

如果存储器维持自己的状态,并且能够调用网络和数据存储服务... ... 在这种情况下,操作只是愚蠢的消息传递者,

- 或者-

... 存储是否应该是来自操作的不可变数据的哑接收者(操作是在外部源之间获取/发送数据的那些操作?这个实例中的存储将充当视图模型,并且能够在根据操作提供的不可变数据设置自己的状态之前聚合/过滤它们的数据。

It seems to me that it should be one or the other (rather than a mix of both). If so, why is one preferred / recommended over the other?

26563 次浏览

存储应该完成所有工作,包括获取数据,以及向组件发出存储数据已更新的信号。为什么?因为操作可以是轻量级的、一次性的和可替换的,而不会影响重要的行为。所有重要的行为和功能都发生在商店中。这还可以防止在两个非常相似但不同的操作中复制行为的重复。这些商店是你处理真相的 单身来源。

在每一个 Flux 实现中,我都看到 Actions 基本上是将事件字符串转换为对象,就像传统上你会有一个名为“锚: 点击”的事件,但是在 Flux 中,它会被定义为 AnchorActions。一拍即合。它们甚至非常“笨”,以至于大多数实现都有单独的 Dispatcher 对象来实际将事件分派给正在监听的存储。

个人而言,我喜欢 Reflux 的 Flux 实现,它没有单独的 Dispatcher 对象,而 Action 对象自己进行分派。


编辑: Facebook 的 Flux 实际上引入了“动作创造者”,所以他们确实使用了聪明的动作。他们也使用存储器准备有效载荷:

Https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/actions/chatmessageactioncreators.js#l27 (第27和28行)

完成后的回调将触发一个新的操作,这一次将获取的数据作为有效负载:

Https://github.com/facebook/flux/blob/19a24975462234ddc583ad740354e115c20b881d/examples/flux-chat/js/utils/chatwebapiutils.js#l51

所以我想这是更好的解决办法。

我已经看到了通量模式的两种实现方式,并且在我自己完成了这两种方式(最初采用前一种方式)之后,我相信存储应该是来自动作的数据的哑接收者,而且写入的异步处理应该存在于动作创建者中。(Async reads can be handled differently)根据我的经验,这样做有几个好处,按重要性排序:

  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中提到的,如果您的存储具有同步操作分派处理(并且它们应该具有同步操作分派处理) ,那么您将需要触发额外的操作来处理异步操作的结果。在操作创建器中执行分派意味着单个操作创建器可以通过处理异步数据访问本身的结果来分派所有三种操作类型。

我在推特上向 Facebook 的开发者们提出了这个问题,从比尔 · 费舍尔那里得到的答案是:

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

But when you have a ticker or some other non-human driver, a call from the store works better.

重要的是在错误/成功回调中创建一个操作,这样数据总是从操作开始

Gaeron 的 通量-反应-路由器-演示有一个“正确”方法的很好的实用变体。

An ActionCreator generates a promise from an external API service, and then passes the promise and three action constants to a dispatchAsync function in a proxy/extended Dispatcher. dispatchAsync will always dispatch the first action e.g. 'GET_EXTERNAL_DATA' and once the promise returns it will dispatch either 'GET_EXTERNAL_DATA_SUCCESS' or 'GET_EXTERNAL_DATA_ERROR'.

如果你想有一天拥有一个与 Bret Victor 的著名视频 原则上的发明相媲美的开发环境,你应该使用哑存储,它只是一个数据结构中的动作/事件的投影,没有任何副作用。如果您的存储实际上是相同的全局不可变数据结构(如 复制中)的成员,也会有所帮助。

更多解释: https://stackoverflow.com/a/31388262/82609

我会提供一个支持“愚蠢”行为的论据。

通过将收集视图数据的责任放在 Actions 中,可以将 Actions 与视图的数据需求耦合起来。

相比之下,通用 Actions (声明性地描述用户的 意图或应用程序中的某种状态转换)允许任何应答该 Action 的 Store 将意图转换为专门为订阅到它的视图定制的状态。

这使得商店数量更多,但规模更小,更加专业化

  • this gives you more flexibility in how views consume Store data
  • “智能”商店专门为消费它们的视图服务,对于复杂的应用程序来说,它将比“智能”操作(可能有很多视图依赖于它)更小、耦合性更差

存储的目的是向视图提供数据。“ Action”这个名称告诉我,它的目的是描述应用程序中的变更。

假设您必须将一个小部件添加到现有的 Dashboard 视图中,该视图将显示您的后端团队刚刚推出的一些新奇的聚合数据。

使用“智能”操作,您可能需要更改“刷新仪表板”操作,以使用新的 API。然而,抽象意义上的“刷新仪表板”并没有改变。视图的数据需求是已经更改的内容。

使用“哑”操作,您可以为新的小部件添加一个新的存储来使用,并将其设置为当它接收到“ refresh-dashboard”操作类型时,它发送对新数据的请求,并在新小部件准备好后将其公开给新小部件。对我来说,当视图层需要更多或不同的数据时,我修改的内容就是数据的来源: 存储。