React Flux 调度程序与 Node.js EventEmitter - 可扩展?

React Flux dispatcher vs Node.js EventEmitter - scalable?

当您使用 Node 的 EventEmitter 时,您订阅了一个事件。您的回调仅在触发特定事件时执行:

eventBus.on('some-event', function(data){
   // data is specific to 'some-event'
});

在 Flux 中,您向调度程序注册您的商店,然后在调度每个事件时调用您的商店。商店的工作是过滤它收到的每个事件,并确定该事件对商店是否重要:

eventBus.register(function(data){
   switch(data.type){
      case 'some-event':
            // now data is specific to 'some-event'
         break;
   }
});

In this video,主持人说:

"Stores subscribe to actions. Actually, all stores receive all actions, and that's what keeps it scalable."

问题

为什么以及如何将每个操作发送到每个商店 [大概] 比仅将操作发送到特定商店更具可扩展性?

这里提到的可扩展性更多的是关于扩展代码库,而不是根据软件的速度进行扩展。流量系统中的数据很容易跟踪,因为每个商店都注册到每个操作,并且操作定义了系统中可能发生的每个应用程序范围的事件。每个商店都可以确定它需要如何更新自身以响应每个操作,而无需程序员决定将哪些商店连接到哪些操作,并且在大多数情况下,您可以更改或阅读商店的代码而无需担心关于它如何影响任何其他商店。

At some point the programmer will need to register the store. The store is very specific to the data it'll receive from the event. How exactly is looking up the data inside the store better than registering for a specific event, and having the store always expect the data it needs/cares about?

系统中的操作表示系统中可能发生的事情,以及该事件的相关数据。例如:

  • 有用户登录;附带用户个人资料
  • 一位用户添加了一条评论;带有评论数据,它被添加到的项目ID
  • 一个用户更新了一个post; post 数据

因此,您可以将操作视为商店可以了解的事物的数据库。任何时候发送一个动作,它都会被发送到每个商店。因此,在任何给定时间,您只需要一次考虑单个存储 + 操作的数据突变。

例如,当 post 更新时,您可能有一个 PostStore 监视 POST_UPDATED 动作,当它看到它时,它会更新其内部状态存储关闭新的 post。这与可能也关心 POST_UPDATED 事件的任何其他商店完全不同——来自任何其他团队的任何其他程序员都可以单独做出该决定,因为他们知道他们能够挂钩任何操作在可能发生的操作的数据库中。

就代码库而言,这是有用且可扩展的另一个原因是控制反转;每个 store 决定它关心什么操作 以及 如何响应每个操作;所有数据逻辑都集中在该存储中。这与像 MVC 这样的模式形成对比,在 MVC 中,控制器被显式设置为调用模型上的变异方法,并且一个或多个 other 控制器可能 also 同时(或不同时间)在相同模型上调用变异方法;数据更新逻辑遍布整个系统,理解数据流需要理解模型可能更新的每个地方。

最后,要记住的另一件事是注册与不注册是一种语义问题;抽象出商店接收所有动作的事实是微不足道的。例如,在 Fluxxor 中,商店有一个名为 bindActions 的方法,它将特定操作绑定到特定回调:

this.bindActions(
  "FIRST_ACTION_TYPE", this.handleFirstActionType,
  "OTHER_ACTION_TYPE", this.handleOtherActionType
);

即使商店收到 所有 操作,在后台它也会在内部映射中查找操作类型并在商店上调用适当的回调。

我一直在问自己同样的问题,从技术上看不出注册如何增加很多,而不是简化。我将提出我对系统的理解,以便希望如果我错了,我可以得到纠正。

TLDR; EventEmitter 和 Dispatcher 服务于类似的目的 (pub/sub),但它们将重点放在不同的功能上。具体来说,Eve​​ntEmitter 不提供 'waitFor' 功能(允许一个事件处理程序确保已调用另一个事件处理程序)。 Dispatcher 已将精力集中在 'waitFor' 功能上。


系统的最终结果是向商店传达一个动作已经发生。商店是 'subscribes to all events, then filters' 还是 'subscribes a specific event'(在调度程序处过滤)。应该不会影响最终结果。数据在您的应用程序中传输。 (处理程序总是只切换事件类型和进程,例如,它不想对所有事件进行操作)

如你所说"At some point the programmer will need to register the store."。这只是订阅保真度的问题。例如,我认为保真度的变化不会对 'inversion of control' 产生任何影响。

facebook 的 Dispatcher 添加的(杀手级)功能是它能够 'waitFor' 不同的商店,首先处理事件。问题是,这个功能是不是要求每个store只有一个event handler?

让我们看看这个过程。当您在 Dispatcher 上发送一个动作时,它(省略一些细节):

  • 迭代所有注册订阅者(到调度程序)
  • 调用已注册的回调(每个商店一个)
  • 回调可以调用'waitfor()',并传递一个'dispatchId'。这在内部引用了由不同商店注册的回调。这是同步执行的,导致其他商店接收到操作并首先更新。这要求在处理操作的代码之前调用 'waitFor()'。
  • 'waitFor' 调用的回调打开操作类型以执行正确的代码。
  • 回调现在可以 运行 它的代码,知道它的依赖项(其他商店)已经更新。
  • 回调打开操作 'type' 以执行正确的代码。

这似乎是一种允许事件依赖的非常简单的方法。

基本上所有的回调最终都会被调用,但是有特定的顺序。然后切换到只执行特定代码。因此,就好像我们只以正确的顺序触发了每个商店的 'add-item' 事件的处理程序。

如果订阅处于回调级别(而不是 'store' 级别),这仍然可能吗?这意味着:

  • 每个商店都会为特定事件注册多个回调,并保持对它们的引用 'dispatchTokens'(与当前相同)
  • 每个回调都有自己的 'dispatchToken'
  • 用户仍然会'waitFor'一个特定的回调,但是是特定商店的特定处理程序
  • 然后调度程序只需要以相同的顺序调度到特定操作的回调

可能 facebook 的聪明人已经发现,增加单个回调的复杂性实际上会降低性能,或者它可能不是优先事项。