从React到Flux架构

React 本身只涉及UI层,如果搭建大型应用,必须搭配一个前端框架。即:React + 前端框架。Flux是Facebook用户建立客户端Web应用的前端架构, 它通过利用一个单向的数据流补充了React的组合视图组件,
相对于框架而言它更像是一种模式思想,在无需许多新代码的情况下就可以构建出合适的Web应用。

应用的几个部分

应用通常分为Dispatcher调度 、存储Store和视图View(React 组件)。分析如下:

1) View: 应用的界面。这里创建响应用户操作的action。

2) Dispatcher: 中心枢纽,传递所有的action,负责把它们运达每个Store,整个数据流程的管理者。

3) Store: 维护一个特定application domain的状态。它们根据当前状态响应action,执行业务逻辑,同时在完成的时候发出一个change事件。这个事件用于view更新其界面。

4) Controller Views:React组件,从Store获取状态,并将其逐级向下传递给子组件。

这几个部分都是通过Action来通信的。控制器controller-view(控制器-视图)模式,视图通常在一个结构顶部,这种结构是用来从存储stroe获得数据,然后将数据传递到自己的子结构。可以简单的将控制器视图理解为React组件,这些组件会监听change事件,如果事件发生了, 就从Stores中获取应用程序新的状态数据。随后,可以将数据通过props逐级向子组件传递。控制器被存储反转控制:存储接受更新,适当地调节这些更新,而不是一致地依赖外部更新其数据,存储之外根本不知道它是如何管理领域数据的,这有助于实现一种清晰的分离关注。存储并没有直接的类似setAsRead()之类的方法,而是只有一个单一方式获取数据到其自成一体的世界中,这个方式就是回调,注册在dispatcher中的callback。

行为创建器(Action Creators)是一组会在视图中被调用的方法(也可以在其他地方使用), 它们用于向Dispatcher发送actions。也就是说,我们使用Dispatcher分发的payloads,实际上就是actions。
Action Creators是相对独立的,它作为语法上的辅助函数以action的形式使得dispatcher传递数据更为便利。

应用的每一个部分可以对应于一个Store,Store可以用来管理数据,定义数据检索方法,此外,Store中还包括了注册给Dispatcher的回调函数。控制器视图(Controller View)会监听我们的Store, 并利用这一点通过发出事件方式通知控制器视图应用程序的状态发生了改变, 从而进行视图层的重新渲染。

每一个 store 的角色是提供状态信息,它与相关的 UI 和成为关联点为了状态的更新。这并不意味着整个业务逻辑需要被实现在 store 本身。每当一个状态更新(的事件)发生, 自动更新 UI 的机制就被触发, 这是 Flux 的一个核心理念。它保证了 UI 始终显示最新的状态,并且可以摆脱一直需要(手动地)维护这些代码的工作。这一步类似于在 MVVM 架构中,将一个视图绑定到 ViewModel。如果要自己实现部分 UI 更新的代码可以借用ReactiveCocoa,它提供了很多操作 (skipUntil,take,map,其他。),很容易就能创建这些关系。但此处不做过多的讨论。


示例代码可以参考阮一峰写的demo.

数据的”单向流动”

数据总是”单向流动”,任何相邻的部分都不会发生数据的”双向流动”。

数据的流程可以分为以下步骤:

  1. 用户访问 View
  2. View 发出用户的 Action
  3. Dispatcher 收到 Action,要求 Store 进行相应的更新
  4. Store 更新后,发出一个”change”事件
  5. View 收到”change”事件后,更新页面

通过应用的数据流是一个方向,没有两边绑定(two-way bingding:Angular.js有此方式),应用状态在存储中维护,允许应用不同部分保持解耦,在存储之间发生依赖的地方,它们能够保持严格的层次关系(设计原则:尽量松耦合,无法回避的就变成树形层次结构),同步管理由dispatcher负责。而two-way绑定会导致级联更新,当一个对象改变导致另外对象改变,接着会触发更多的更新,当应用规模增长时,这些级联更新会使得预期响应用户交互的结果变得困难,当更新只会在一个轮回中发生改变数据时,整个系统就变得可预期。

不过,很多场景下不仅仅包括应用程序内部的状态数据,还可能包括来自外部的数据,即常见的RESTful服务,在Action中引入外部数据流是个不错的方式,然后把数据送到Store中。官方的示例代码很好的为我们解决了疑惑。

数据流

Dispatcher接收actions作为payloads,并且将payloads分发给所注册的回调函数。并且包括了允许你以指定次序调用这些回掉函数的执行逻辑,例如在处理前等待数据更新。在Flux架构中只有一个Dispatcher,它是你整个应用的中央Hub。在下面的handleTestAction方法中调用了dispatch方法(该方法来自Dispatcher实现),它会将payloads广播给所有注册到它的回调函数 (注意,这里所说的回调是在store中注册给Dispatcher的)。然后,action会触发Store中相应事件,并最终体现在state的更新上。示例如下:

//创建一个简单的Dispatcher
var Dispatcher = require('flux').Dispatcher;
var AppDispatcher = new Dispatcher();

AppDispatcher.handleTestAction = function(action) {
  this.dispatch({
    source: 'Test_ACTION',
    action: action
  });
}

module.exports = AppDispatcher;

Dispatcher模块能够定义依赖,并在Store中向Dispatcher注册回调函数。如果你的应用的某个部分依赖于其他部分的数据更新,为了能够进行合适的渲染,Dispatcher中的waitFor方法将会非常有用。在Store中存储注册给Dispatcher的回调函数的返回值.如果一个相关的action发生了,那么就触发一个change事件,并且监听 此事件的视图会根据新的状态(state)进行重新渲染。

TestStore.dispatcherIndex = AppDispatcher.register(function(payload) {});//dispatcherIndex 存储注册给Dispatcher的回调函数的返回值

在Store中处理每一个被分发的action时,我们可以使用Dispatcher的waitFor方法来确保我们的ShoeStore已经被更新了,示例代码如下:

case 'Do_Something':
  AppDispatcher.waitFor([
    TestStore.dispatcherIndex
  ], function() {
    CheckoutStore.purchaseShoes(TestStore.getSelectedShoes());
  });
  break;

在Facebook提供的例子中,action的类型常数被用于定义这些action所被应用的场景,并且是伴随着action一起被传递的。现在,在所注册的回调函数内部,这些actions可以根据他们的action类型来处理。

总结来说,Flux 模式首先会定义状态类型的形状。之后会定义 actions。然后实现业务逻辑和针对每个 action 状态的转变 - 这个实现在 store 中。最后实现 UI 绑定方法,将状态映射到视图展示层。Flux 主要的好处在于它把有关的内容严格的区分开。这让测试业务逻辑和大块的 UI 代码变得非常容易。测试时需要关注两个逻辑:在 store 中的业务逻辑和视图模型的提供者 (就是那些我们实现的类似 React 的方法,他们基于输入的状态描述了 UI)。但是,国内对这方面并不是很关注,关于测试,此处不做太多的讨论。有需要的可以看下这篇文章(可能需要翻墙!)

总结

通过上面的分析,我们会发现Flux的React应用开发类似于jQuery的DOM遍历操作。没有Flux可以照常进行React应用开发,但是你在代码组织和数据流程上会缺乏优雅的解决方案。Flux可以把接受数据和发送数据分离。因此当出错的时候可以很容易地沿着数据流看看到底是哪里出错了。

如果你想使用FLux架构,但你并不想使用React,那么你可以参考 Delorean,它是一个Flux框架,并且可以让你使用Ractive.js或者Flight。另一个值得参考的库是 Fluxxor,它实现了在紧耦合的Flux组件中包含一个中央Flux实例。

Flux让事情变得可预见。

注:本文受到了《Flux For Stupid People》《Turning UIKit inside out》的启发。在此对其表示感谢!