[译] Flux 概述

version 0.0.1

对于我这样的英语白痴来说想一次性看懂 Flux 在讲什么实在太难了,所以我决定把他一点一点地翻译过来,并随着我的慢慢使用逐渐改善我对他的认识,然后再慢慢改进这篇翻译。
原文:https://facebook.github.io/flux/docs/overview.html

概述

FLux 是 Facebook 用来构建客户端 web 应用的架构。它使用一种单向数据流来补足 React 的视图控件。她更像是一种模式,而非一种正式的框架。你可以立刻开始使用 Flux,不需要补充太多的代码。

Flux 应用有三个主要的部分:dispatcher(派发器)、stores 和 views(React 组件)。不要把她和 MVC(Model-View-Controller) 混为一谈。Controller 确实存在在 Flux 应用中,但它是 Controller-View(控制器视图)。所谓的控制器视图指的是在架构的顶端,从 stores 中获取数据,并且把这些数据传递给他的子视图的视图组件。另外,Action(动作)的产生者(dispatcher 的辅助方法)被用于提供一个语义化的API。这个API用来描述应用中所有可能的变化。你可以把她想成是 Flux 的更新周期中的第四个部分。

不同于 MVC,Flux 使用了一种单向数据流。当一个用户和 React 视图交互的时候,视图通过一个中心 dispatcher(派发器)来派发一个Action(动作)到各个不同的 store,这些 store 掌管着应用数据和业务逻辑。这些 store 的改变会更新所有受到他们影响的视图层。这种模式和 React 的声明式编程模式配合地非常好。React 的编程模式使得 store 可以在不用关心如何指定不同状态的视图如何变化的情况下推送更新。

Flux 天生适合处理那些派生的复杂数据。举例来说,我们想要在显示未读帖子数量的同时,在另一个视图中展示主题列表,其中未读的主题高亮。这对于 MVC 来说很难处理,标记单个帖子已读会更新帖子的数据模型,然后还需要更新未读数量的模型。这些依赖和级联更新经常会导致 MVC 应用十分臃肿,使得数据流乱作一团,结果不可预测。

在 Flux 中,Control 的作用被 store 所反转。store 接受更新并且适当地处理他们,而不是依赖 store 以外的东西来更新 store 里的数据。如何处理 store 内部的数据对 store 外部透明,这使得每个部分都分工明确。Store 没有像 setAsRead() 直接的设置数据的方法。取而代之的是,有一种单一的方法,让新的数据进入 store 的世界,通过他们在 dispatcher 中定义的回调函数。

结构和数据流

在 Flux 中的数据按照一个方向流动。

单向数据流是 Flux 模式的核心。上面的图表应该是 Flux 编程者最基本的心智模型。dispatcher(派发器)、stores 和 views(React 组件)是各自独立的节点,有着明确的输入输出。actions 是一些简单的对象,包含新的数据和一个可以辨识的 type 属性。

根据用户交互,视图可能会产生一个新的 action 在系统中传输。

所有的数据都会流经 dispatcher(派发器)。action 通过一个 action creator 方法被传递给 dispatcher(派发器),这些 action 大部分来自视图层上的用户交互。dispatcher 会调用 store 注册在其中的回调函数,把 action 发给所有的 store。在这些 callback 中, store 需要指定那些类型的 action 和自己维护的 state 相关。然后,store 会发出一个 change 事件来告诉控制器视图,数据层产生了一个变化。控制器视图会调用自己的 setState() 方法来重新渲染自己和自己的子控件。

这种结构使得我们可以轻易地推出一个应用,以一种让人联想起函数式反应编程,或者更确切地说,数据流编程或基于流的编程的方式。在这种方式中,数据按照一个方向流动,没有双向绑定。应用的状态仅仅在 store 中维护,使得应用的不同部分更加解耦。在 store 之间的依赖真得产生的时候,他们也是通过 dispatcher 统一管理并且同步更新,从而维持了一个严谨的结构。

我们发现双向数据绑定会导致级联更新的发生。改变一个对象会导致另一个对象的改变,从而触发更多的更新。当一个应用逐渐变大,这些级联变化会使得预测一个用户交互会导致怎样的结果变得非常困难。当更新只能通过一个单向的环来改变数据,系统整体就变得更加可预测。

让我们来看看 Flux 的各个部分。我们最好从 dispatcher(派发器)开始。

唯一的派发器

在 Flux 应用中,dispatcher(派发器)是管理所有数据流的中心节点。她本质上是一个 store 的回调函数登记处,自身并没有什么功能 —— 它仅仅是把 actions 派发给 store,每个 store 注册自己并且提供一个回调函数。当 action 生成器提供给 dispatcher 一个新的 action,所有 store 都会通过注册在 dispatcher 里的回调函数收到这个 action。

随着应用的扩展,dispatcher 变得越来越重要,因为他可以通过指定的顺序来唤起回调函数,从而管理 store 之间的依赖。Store 可以以声明的方式等待其他 store 更新后,再更新自己。

你可以从 npmBower 或者 Github 上获取到和 Facebook 使用的相同的 dispatcher。

Store

Store 包含应用的状态和逻辑。他们的定位和传统 MVC 框架中的 model 层类似,但是他们管理很多对象的状态。他们并不是像 ORM 模型那样只负责数据的记录,而且他们也不同于 Backbone 的 collections。除了管理 ORM 型的数据之外,他还掌管着某个专门领域的应用状态。

举个例子,Facebook 的回放视频编辑器使用了一个 TimeStore 来记录播放时间和播放状态。另一方面,同一个应用的 ImageStore 记录了图片的集合。在我们的 TodoMVC 例子里的 TodoStore 类似地管理着 to-do 项目的列表。 Store 同时具有了数据模型集合和逻辑模型的特性。

正如上面提到的,Store 向 dispatcher 注册自己并提供给 dispatcher 一个回调函数。这些回调函数会接受 action 作为参数。在回调函数中,我们使用 基于 action 类型的 switch 声明来解释这些 action 并提供和 store 内部方法相连的正确钩子。这就使得 action 可以通过 dispatcher 来更新 store 内部的状态。这些 store 更新后,会广播一个事件来宣告自己的状态更新了,这样视图层就可以请求这些新的状态,并且更新自己。

View 和 Controller-View

React 在视图层为我们提供了一种可组合和任意重用的视图。在视图架构的顶层,有一种视图监听他依赖的所有 store 派发的事件。我们称之为 Controller-View, 因为他提供了胶水代码来获取 store 中的数据,并将之逐层传给他的子视图。我们用这种 Controller-View 来管理页面某些重要的部分。

当他从 store 接收到一个事件,他会首先通过 store 的共有 getter 方法来请求数据,然后他会调用自己的 setState 或者 forceUpdate 方法, 来触发自身和子视图的 render 方法。我们经常会沿着视图的链条将 store 的整个状态传递下去,使得不同的子视图可以获得他们想要的数据。这样做除了能够保持顶层架构的 controller 行为外,也能够保证子视图在功能上尽可能的纯净。而且这样做也能够减少我们需要管理的 prop 的状态。有时我们需要在视图架构更深的地方增加 Controller-View 来使组件保持简单。这样做也许会帮助更好地压缩某个指定数据领域的架构。但要注意的是,新的 Controller-View 会引入新的,可能会产生冲突的数据流入口。在考虑是否要增加一个新的 Controller-View 的时候,要权衡更简单的组件带来的好处和多数据更新流带来的复杂度。这种多重数据更新会导致 React 的 render 方法被不同 Controller-View 重复调用,可能会增加调试的困难度。

Action

Dispatcher 暴露了一个方法使我们可以向 store 做一个派发,并且传递一些数据给 store。这些数据我们称之为 Action。我们使用一些辅助方法来产生这些 action 并将之传递给 dispatcher。例如,我们可能想要在一个 to-do 列表应用中改变一个 to-do 条目的文案。我们会在 TodoActions 模块中使用一个类似于 updateText(todoId, newText) 的函数来生成 action。这个方法可能会被我们视图层的事件回调函数调起,因此我们可以通过用户交互来调用它。Action 的生成方法会给这个 action 增加一个 type,使得他可以在 store 中被正确解读。在我们的例子中,这个类型也许会被叫做 TODO_UPDATE_TEXT

Action 也可能从别的地方产生,例如服务器。举个例子,这个行为也许会在数据初始化时产生。这个行为也可能在服务器返回错误代码或者有新的更新提供给应用时发生。

那么那个 Dispatcher 呢。

正如稍早前提到的,Dispatcher 也能够管理 store 之间的依赖。这个功能是通过 Dispatcher 类里的 waitFor() 方法来实现的。我们不要在这个极其简单的 TodoMVC 应用中使用这个方法。但是在更大、更复杂的项目中,waitFor 方法便变得非常重要。

在 TodoStore 的注册回调函数中,我们可以在继续执行之前明确地等待任何其他的 store 更新完成。

case 'TODO_CREATE':  
  Dispatcher.waitFor([
    PrependedTextStore.dispatchToken,
    YetAnotherStore.dispatchToken
  ]);

  TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
  break;

waitFor 方法接受一个由登记号码组成的数组作为参数。这些号码通常被称作 dispatch tokens(派发口令)。因此调起 waitFor 方法的 store 可以等待另一个 store 更新完成来根据情况更新自己的状态。dispatch tokens 是注册回调函数时的返回值。

PrependedTextStore.dispatchToken = Dispatcher.register(function (payload) {  
  // ...
});

想了解更多关于 waitFor(), actions, action creators 和 the dispatcher 的知识,请查看 Flux: Actions and the Dispatcher.