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

IT技术 reactjs reactjs-flux
2021-04-11 02:59:32

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

-或者-

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

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

6个回答

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

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

  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});
      });
    }
    

    否则可能会在各种动作中重复的逻辑应该被提取到一个单独的module中;在本例中,该module将是SomeDataAccessLayer,它处理执行实际的 Ajax 请求。

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

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

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

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

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

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

@SharpCoder 我想如果您有实时收报机或类似的东西,您实际上并不需要触发操作,当您从商店执行此操作时,您可能需要编写更少的代码,因为商店可以立即访问状态& 发出变化。
2021-05-30 02:59:32
虽然这是有道理的,但知道为什么a call from store works better when action triggers from non-human driver 吗?
2021-06-19 02:59:32

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

在我见过的每个 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/ChatMessageActionCreators.js#L27(第27和28行)

完成时的回调将在这次以获取的数据作为有效负载触发新操作:

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

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

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

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

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

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

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

  • 这使您在视图如何使用 Store 数据方面具有更大的灵活性
  • “智能”商店,专门用于消费它们的视图,与“智能”操作相比,将更小,复杂应用程序的耦合更少,许多视图可能依赖于“智能”操作

Store 的目的是为视图提供数据。名称“Action”向我表明其目的是描述我的应用程序中的更改。

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

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

使用“哑”操作,您可以为要使用的新小部件添加一个新 Store,并设置它以便当它收到“刷新仪表板”操作类型时,它会发送对新数据的请求,并将其公开给准备好后的新小部件。对我来说,当视图层需要更多或不同的数据时,我更改的内容是该数据的来源:Stores。

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

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