ZZZero自翻,转载请注明出处。
原文:http://facebook.github.io/flux/docs/overview.html#content

更新日志:2014-12-17 18:00:51 尼玛才发现视频地址贴错了(:з」∠)……

概述

Flux是Facebook用来构建web客户端应用的一个应用框架,它通过单向数据流补充了React的可组合的视图组件(view components)。它更多的是一种样式而非一个正式框架,你不需要很多新代码就可以立刻开始使用Flux。

http://youtu.be/nYkdrAPrdcw?list=PLb0IAmt7-GS188xDYE-u1ShQmFFGbrk0v

(观看视频需翻墙)

通常一个应用由三个重要组成部分:调度器(the dispatcher)、存储器(the stores)、视图(the views)【由React构成】。这些东西不应该和Model-View-Controller混淆在一起。控制器(Controller)存在于Flux应用中,但是他们是controller-views —— 视图(views)经常通过搜寻整个架构的最上层去检索存储器中的数据,并通过这些数据寻找到他们相应的后代。此外,用户行为 —— 调用辅助方法 —— 经常使用支持语义化的调度API。在Flux上传循环的第四部分中是一个很实用的想法。

Flux避开了MVC选择了单向数据流。当用户和React视图(React view)交互时,视图将这一行为通过核心调度器传递给各种存储器中从而控制应用数据和事务逻辑,更新受其影响的视图。这种运行方法尤其适合React所使用的这种声明式编程风格。这使得存储器能直接发送更新而不需要具体编写如何在视图和状态间相互转化。

我们起初在派生数据之间安排一套得体的协议:举个栗子~,当另一个视图展示一个消息线程列表,以及关于未读消息的一个强调时我们想要展示一个未读消息的数量。这对MVC模式来说相当难以管理 —— 标记一个线程为已读需要更新线程模型,然后还需要更新未读数的模型。这些数据依赖和级联更新经常发生于大型的MVC应用中,导致数据流迂回混乱且产生不可预测的结果(:з」∠)……

控制器是颠倒的存储器:由存储器接受更新并核对他们是否正确,而不是一般情况下的依靠某些外部的东西去更新数据。外在的存储器无法了解它领域中所管理的数据,有助于保持明确地分离的关系。这也使得存储器比模型更具有可测试性,尤其是在存储器没有类似setAsRead()这种直接调节器的方法以来,但是作为替代仅有一个负载输入点,可以实现穿过调度器和起源动作。

结构和数据流

数据在Flux应用中单向流动,形成一个循环:

视图 ——》(动作)——》调度器 ——》(注册回调)——》存储器 ——》(存储器发出“change”事件)——》controller-views “change”事件处理 ——》视图……

一个单向的数据流是Flux样式的核心,而且事实上Flux这个名字就是来源于拉丁语中的”流动“。在上图(表?)中,调度器、存储器、视图都是独立的节点,且写入输出清晰。动作只是简单分离的,语义将帮助函数更快的将数据传递给调度器。

所有的数据流作为中央枢纽穿过调度器。动作大多经常发起于用户和视图的交互中,不会经常访问到调度器内部。这时调度器则使用存储器中已经注册的回调,有效的在动作和所有存储器之间传递数据负载。在它们注册的回调里,由存储器查出哪个动作是相关联的,并予以相应的答复。之后存储器发出”change“事件警告controller-views数据层已经改变。Controller-views监听到这些事件并且从事件处理程序中的存储器里获取数据。Controller-views通过setState()或者forceUpdate()调用它们自己的render()方法以更新它们自身以及它们的子节点。

这个架构看起来使我们的应用某种程度上很像是函数式反应编程(FRP),更具体地说是数据流编程或者是基于流的编程,任何地方的数据流都沿单一方向穿过应用 —— 它们没有双向绑定。应用的状态仅维持在存储器里,允许应用的各个部分保持高度解耦。某些地方存储器之间确实发生了依赖,它们保持在一个层级分明的环境下,由调度器完成同步更新。

我们发现双向绑定数据会导致级联更新,当改变某个对象会导致其他对象的改变,其中也可能会引发更多地更新。随着应用的日益庞大,这些级联更新会导致很难预测到用户的交互行为会产生怎样的结果。当更新能只单轮改变数据内部,整个系统会变得更加可预测。

那么从调度器开始,让我们来零距离看看Flux更新循环的各个部分~

单线程调度器

Flux应用中调度器是管理所有数据流的中心枢纽。本质上说它就是个存储器中回调的注册表。每一个存储器都登记着其自身以及其所提供的回调。当调度器反馈一个动作,应用中所有的存储器会由注册表中的回调发送当前动作所对应的数据负载。

当应用变得庞大时,调度器会变得更加重要,它可以在特定命令中的通过注册回调去处理存储器之间的依赖。存储器能够声明等待其他的存储器完成更新,再相应的进行自我更新。

存储器

存储器包含了应用状态和逻辑。他们的职责有几分类似于传统MVC中的模型(moder),但是他们掌管着许多对象的状态 —— 而不是一个对象的实例。也不是类似于Backbone的集合。存储器在应用中的某个特定领域内管理应用状态,而不是简单地管理一个ORM风格的对象集合。

举个栗子~,Facebook的Lookback Video Editor利用了时间存储器去保持对回放的时间点和回放的状态的跟踪。另一方面,相同应用中图像存储器则保持对图像集合的跟踪。待办事项存储器在我们的待办事项MVC案例中是一个于管理一个待办事项项目的集合。一个存储器同时表现出模型中集合和逻辑域中单个模型两种特性。

提到上述内容,一个存储器注册其自身关联的调度器并提供一个相关回调。回调接受动作的数据负载作为参数。这个负载包含有一类属性,以确定动作的类型。这个存储器所注册的回调中,有个基于动作类型的开关指令去用来解释负载和提供适当的挂钩去进入到存储器的各种内部方法。这使得动作可以通过调度器直接更新存储器的状态。由于存储器更新,它们传播出一个事件通知它们的状态已经改变,这样这些视图就可以查询新的状态并更新自己。

视图(Views)和Controller-Views

React为我们所需的视图层提供了一套视图组件。其实只要在靠近整个视图结构嵌套的顶端,有一个视图监听事件在其所依赖的存储器里传播。那么任何人都可以称其为controller-view,它提供了中间代码去获取存储器中得数据并通过这个数据向下延伸到它整个链条的后代。我们应该有一个controller-views去调节页面中每一个重要的区域。

当它从存储器收到事件的时,首先需要通过存储器中的公共获取方法去请求一个新数据。然后调用它自身的setState()forceUpdate()方法,从而让它自身的render()方法和render()方法下所有的后代都运行起来。

我们经常通过所有状态下的单个对象存储视图链,以便于分配不同的后代去做他们需要做的事。此外保持controller-like行为位于框架顶层,这样就能保持我们的子视图在功能上尽可能的纯净,在单个对象中继承全部状态下的存储器也能降低我们工作量。

偶尔我们也会需要在更深的层级去添加额外的controller-views以保持组件简洁。这也许会帮组我们去更好的封装这个区域的结构层次于特定数据域的联系。注意,无论如何,在更深层的结构层次中可以违反单向数据流去添加一个新的controller-views,这也许是数据流中冲突的突破点。在决定是否添加深度controller-view上,权衡好利弊,简单组件反对复杂的多数据更新流在不同的点上深入结构层级。这些多数据更新会导致各种奇怪的结果,由于React的给予方法获取请求反复更新来自不同controller-views,这可能会增加调试的难度。

动作(Actions)

调度器有一个方法允许视图触发调度到存储器,并包括一个数据负载或一个动作。这个动作结构可能包装在发送负载到调度器的语义的辅助方法中。举个栗子~,我们或许想要改变一些待办列表应用中的待办事项的文本。我们将创建一个动作和一个函数签名类似updateText(todoId, newText)在我们的TodoActions模块中。这个方法也许来自内部我们的视图的事件操控器,使得我们可以在相应用户行为中调用它。这个动作方法也补充了动作类型的负载,以至于当这个负载在存储器中被解析时,他能响应一个合适的负载和一个特别的动作类型。在本文我们只关心最基本的数据流。

如何理解调度器?

我们早就提到,调度器同样能够处理存储器之间的依赖,这个功能可以通过在调度类的waitFor()方法实现。在十分简单地待办事项MVC应用内我们不需要去用这个方法,但是我们将它包含在案例调度器中(尼玛是404……扎克伯格食我大雕(╯‵□′)╯︵┻━┻)讲述一个调度器应该更加大型综合应用中的作用。

待办事项存储器注册的回调里我们可以明确的等候运行之前的任何依赖去优先更新:
[javascript]case ‘TODO_CREATE’:
Dispatcher.waitFor([
PrependedTextStore.dispatcherIndex,
YetAnotherStore.dispatcherIndex
], function() {
TodoStore.create(PrependedTextStore.getText() + ‘ ‘ + action.text);
TodoStore.emit(‘change’);
});
break;
[/javascript]
waitFor()这个参数是一个调度器注册索引的数组,和一个给定的索引已完成的回调之后最终回调的调用。因此被调用waitFor()的存储器可以依赖其他存储器的状态来通知它应该如何更新其自己的状态。

如果我们建立了一个循环依赖那么就会产生一个问题。如果存储器A等待存储器B,并且B也在等待A,然后我们会得到一个十分糟糕的情况。这时我们将需要一个更强大的调度器去标记这些循环的依赖以便于控制跟踪控制台错误,而且这个需求并不容易实现。不幸的是,这个问题有一丁点超出该文档的范围。在不久的将来我们希望这个范围能覆盖如何建立更多强大的调度器以及如何初始化、更新、保存固定数据和应用状态,如同web服务器API一样。