【问题标题】:Should flux stores, or actions (or both) touch external services?通量存储或操作(或两者)是否应该触及外部服务?
【发布时间】:2014-10-27 03:28:44
【问题描述】:

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

-或-

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

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

【问题讨论】:

  • 这篇文章可能对code-experience.com/…有帮助
  • 对于那些评估通量模式的各种实现的人,我强烈建议看一下 Redux github.com/rackt/redux 存储是作为纯函数实现的,它接受当前状态并发出新版本状态。由于它们是纯函数,因此它们是否可以调用网络和存储服务的问题不在您的掌控之中:它们不能。

标签: reactjs reactjs-flux


【解决方案1】:

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

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

  2. 所有动作分派都来自动作创建者。如果您在商店中处理异步操作并且希望保持商店的动作处理程序同步(并且您应该为了获得通量单-调度保证),您的商店将需要触发额外的 SUCCESS 和 FAIL 操作以响应异步处理。相反,将这些调度放在动作创建者中有助于将动作创建者和商店的工作分开;此外,您不必深入了解您的存储逻辑来确定从何处分派操作。在这种情况下,典型的异步操作可能看起来像这样(根据您使用的 Flux 风格更改 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 中提到的,如果您的商店有同步动作调度处理(它们应该),您将需要触发额外的动作来处理异步操作的结果。在动作创建者中进行分派意味着单个动作创建者可以通过处理异步数据访问本身的结果来分派所有三种动作类型。

【讨论】:

  • 我认为发起 Web api 调用(动作创建者与商店)的原因不如成功/错误回调应该创建动作这一事实重要。因此,数据流始终是:操作 ​​-> 调度程序 -> 存储 -> 视图。
  • 将实际的请求逻辑放在 API 模块中会更好/更容易测试吗?因此,您的 API 模块可以只返回一个您从中分派的承诺。动作创建者只是在发出初始“待定”动作后根据解决/失败进行调度。剩下的问题是组件如何监听这些“事件”,因为我不确定请求状态是否应该映射到存储状态。
  • @backdesk 这正是我在上面的示例中所做的:调度一个初始的待处理操作 ("SOME_ACTION"),使用 API 发出一个返回承诺的请求 (SomeDataAccessLayer.doSomething(userId)),然后在两个.then 函数,调度额外的动作。如果应用程序需要了解状态的状态,请求状态可以(或多或少)映射到存储状态。如何映射取决于应用程序(例如,可能每个评论都有一个单独的错误状态,例如 Facebook,或者可能有一个全局错误组件)
  • @MichelleTilley “flux 的核心概念之一是防止级联调度并防止一次多次调度;当您的商店进行异步处理时,这很难做到。”这对我来说是一个关键点。说得好。
【解决方案2】:

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

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

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

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

【讨论】:

  • 虽然这是有道理的,但知道为什么a call from store works better when action triggers from non-human driver 吗?
  • @SharpCoder 我想如果你有一个实时报价或类似的东西,你真的不需要触发一个动作,当你从商店这样做时,你可能需要编写更少的代码,因为商店可以立即访问状态并发出更改。
【解决方案3】:

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

在我见过的每个 Flux 实现中,Action 基本上都是将事件字符串转换为对象,就像传统上你会有一个名为“anchor:clicked”的事件,但在 Flux 中它会被定义为 AnchorActions.Clicked。它们甚至如此“愚蠢”,以至于大多数实现都有单独的 Dispatcher 对象来实际将事件分派到正在侦听的商店。

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


edit: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

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

【讨论】:

  • 这个 Reflux 实现是什么?我没听说过。你的回答很有趣。您的意思是您的商店实现应该具有执行 API 调用等的逻辑?我认为商店应该只接收数据并更新它们的值。他们过滤特定操作,并更新其商店的一些属性。
  • Reflux 是 Facebook 的 Flux 的一个轻微变体:github.com/spoike/refluxjs Stores 管理应用程序的整个“模型”域,而 Actions/Dispatchers 只将事物拼接和粘合在一起。
  • 所以我一直在考虑这个问题并且(几乎)回答了我自己的问题。我会在这里添加它作为答案(供其他人投票),但显然我在stackoverflow上的业力太差,无法发布答案。所以这里有一个链接:groups.google.com/d/msg/reactjs/PpsvVPvhBbc/BZoG-bFeOwoJ
  • 感谢 google 群组链接,它似乎提供了很多信息。我也更喜欢通过调度程序进行的所有事情,以及商店中非常简单的逻辑,基本上,更新他们的数据就是这样。 @Rygu 我会检查反流。
  • 我用另一个视图编辑了我的答案。似乎两种解决方案都是可能的。我几乎肯定会选择 Facebook 的解决方案而不是其他解决方案。
【解决方案4】:

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

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

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

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

  • 这让您在视图如何使用商店数据方面更加灵活
  • “智能”Stores 专门用于使用它们的视图,对于复杂的应用程序来说,它比“智能”Actions 更小,耦合更少,而许多视图可能依赖于后者

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

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

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

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-24
        • 1970-01-01
        • 2019-12-16
        • 1970-01-01
        • 1970-01-01
        • 2011-11-10
        • 2021-11-14
        相关资源
        最近更新 更多