124

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

-或者-

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

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

4

6 回答 6

151

我已经看到了通量模式的两种实现方式,并且在我自己完成之后(最初使用前一种方法),我相信存储应该是来自操作的数据的愚蠢接收者,并且写入的异步处理应该存在于动作创造者。(异步读取可以以不同的方式处理。)根据我的经验,这有一些好处,按重要性排序:

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

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

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

于 2014-09-03T15:54:41.740 回答
51

我将这个问题发给 Facebook 的开发人员,我从比尔·费舍尔那里得到的答案是:

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

但是当你有一个自动收报机或其他一些非人类司机时,从商店打来电话会更好。

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

于 2014-09-04T04:15:43.857 回答
8

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

在我见过的每个 Flux 实现中,Action 基本上都是将事件字符串转换为对象,就像传统上你会有一个名为“anchor:clicked”的事件,但在 Flux 中它会被定义为 AnchorActions.Clicked。它们甚至如此“愚蠢”,以至于大多数实现都有单独的 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

所以我想这是更好的解决方案。

于 2014-09-02T22:33:30.470 回答
3

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

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

相比之下,以声明方式描述用户意图或应用程序中的某些状态转换的通用 Actions 允许任何响应该 Action 的 Store 将意图转换为专门为订阅它的视图定制的状态。

这适用于更多但更小、更专业的商店。我主张这种风格是因为

  • 这使您在视图如何使用 Store 数据方面具有更大的灵活性
  • 专门针对使用它们的视图的“智能”存储,对于复杂的应用程序而言,比“智能”操作更小,耦合更少,许多视图可能依赖于智能操作

Store 的目的是为视图提供数据。“Action”这个名字对我来说意味着它的目的是描述我的应用程序的变化。

假设您必须向现有的仪表板视图添加一个小部件,该视图显示了您的后端团队刚刚推出的一些精美的新聚合数据。

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

使用 "dumb" Actions,您可以添加一个新的 Store 以供新的小部件使用,并将其设置为当它接收到 "refresh-dashboard" Action 类型时,它会发送对新数据的请求,并将其公开给准备好后的新小部件。对我来说,当视图层需要更多或不同的数据时,我更改的是该数据的来源:商店。

于 2015-10-11T05:15:44.820 回答
2

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

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

于 2015-06-05T05:35:11.203 回答
1

如果您希望有一天拥有与 Bret Victor 的著名视频Inventing on Principle中所看到的开发环境相当的开发环境,您应该使用哑存储,它们只是数据结构中动作/事件的投影,没有任何副作用。如果您的商店实际上是同一个全局不可变数据结构的成员,这也会有所帮助,就像在Redux中一样。

更多说明:https ://stackoverflow.com/a/31388262/82609

于 2015-07-22T09:03:11.810 回答