商店是否应该维护自己的状态并有能力在这样做时调用网络和数据存储服务……在这种情况下,动作只是愚蠢的消息传递者,
-或者-
...商店应该是来自动作的不可变数据的愚蠢接收者(并且动作是在外部源之间获取/发送数据的动作?在这种情况下,商店将充当视图模型,并且能够聚合/过滤它们的数据)数据之前,根据操作提供的不可变数据设置自己的状态。
在我看来,它应该是一个或另一个(而不是两者的混合)。如果是这样,为什么首选/推荐一个而不是另一个?
商店是否应该维护自己的状态并有能力在这样做时调用网络和数据存储服务……在这种情况下,动作只是愚蠢的消息传递者,
-或者-
...商店应该是来自动作的不可变数据的愚蠢接收者(并且动作是在外部源之间获取/发送数据的动作?在这种情况下,商店将充当视图模型,并且能够聚合/过滤它们的数据)数据之前,根据操作提供的不可变数据设置自己的状态。
在我看来,它应该是一个或另一个(而不是两者的混合)。如果是这样,为什么首选/推荐一个而不是另一个?
我已经看到了两种方式实现的通量模式,并且在我自己完成后(最初采用前一种方法),我相信存储应该是来自动作的数据的愚蠢接收者,并且写入的异步处理应该存在于动作创作者。(异步读取可以有不同的处理方式。)根据我的经验,这有一些好处,按重要性排序:
您的商店变得完全同步。这使您的商店逻辑更容易遵循并且非常容易测试——只需实例化一个具有给定状态的商店,向它发送一个动作,然后检查状态是否按预期改变。此外,flux 的核心概念之一是防止级联调度和防止一次多次调度;当您的商店进行异步处理时,这很难做到。
所有动作分派都发生在动作创建者处。如果您在商店中处理异步操作,并且希望使商店的操作处理程序保持同步(为了获得通量单分派保证,您应该这样做),您的商店将需要触发额外的 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 请求。
您需要更少的动作创建者。这不是什么大不了的事,但很高兴拥有。正如#2 中提到的,如果你的商店有同步动作分派处理(它们应该),你需要触发额外的动作来处理异步操作的结果。在动作创建者中进行分派意味着单个动作创建者可以通过处理异步数据访问本身的结果来分派所有三种动作类型。
我在twitter上向 Facebook 的开发人员发送了这个问题,我从比尔·费舍尔那里得到的答案是:
当响应用户与 UI 的交互时,我会在操作创建者方法中进行异步调用。
但是,当您有自动收报机或其他非人类司机时,从商店打来的电话效果会更好。
重要的是在错误/成功回调中创建一个动作,以便数据始终源自动作
存储应该做所有事情,包括获取数据,以及向组件发送存储数据已更新的信号。为什么?因为动作可以是轻量级的、一次性的和可替换的,而不会影响重要的行为。所有重要的行为和功能都发生在商店中。这也防止了重复的行为,否则这些行为会被复制到两个非常相似但不同的动作中。商店是您(处理)真相的唯一来源。
在我见过的每个 Flux 实现中,Actions 基本上都是将事件字符串转换为对象,就像传统上你有一个名为“anchor:clicked”的事件,但在 Flux 中它会被定义为 AnchorActions.Clicked。它们甚至非常“愚蠢”,以至于大多数实现都有单独的 Dispatcher 对象来实际将事件分派到正在侦听的商店。
我个人喜欢 Reflux 的 Flux 实现,其中没有单独的 Dispatcher 对象和 Action 对象自己进行调度。
编辑:Facebook 的 Flux 实际上是在“动作创建者”中获取的,因此他们确实使用了智能动作。他们还使用商店准备有效载荷:
完成时的回调将在这次以获取的数据作为有效负载触发新操作:
所以我想这是更好的解决方案。
我将提供一个支持“愚蠢”操作的论据。
通过将收集视图数据的责任放在您的操作中,您将操作与视图的数据要求相结合。
相比之下,以声明方式描述用户意图或应用程序中的某些状态转换的通用操作允许响应该操作的任何 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”。