Flux架构简介
Flux是Facebook用来开发Web应用的一种架构/模式,Flux可以作为React的补充。
Flux应用由以下主要部分构成:
- Dispatcher,接收Action转发给Store
- Stores,存储应用程序的状态和商业逻辑,状态发生变化后发布事件通知视图更新
- Views,一般以React组件的形式来实现
- Actions,代表用户或者系统的操作,它是简单的JS对象,具有一个表示动作类型的type属性,其它属性则作为代表操作所产生的新数据的载荷
MVC模式中的控制器存在于Flux架构中,其角色由控制器视图(controller-views)来承担。控制器视图通常位于组件树的最顶部,负责从Store获取数据并向下传递给子组件。
单向数据流是Flux架构的核心,对于Flux开发者来说,下图代表了最基本的数据流模型:
作为对用户操作的响应,View可以产生新的Action并在系统中传播:
全局唯一的Dispatcher类似于一个Hub,所有数据流都要经过它。提供给Dispatcher的Action通常由Action creator创建。
通过调用Store注册的回调函数,Dispatcher把Action传递给所有Store。在回调函数中,Store对和自己管理的状态有关的Action进行响应,然后发布一个change事件,通知控制器视图数据层发生了变化。
控制器视图监听Store发布的所有事件,在事件处理器中获得Store的数据。然后,视图控制器调用自己的setState()方法,导致其自身以及子组件的UI更新。
Flux提倡的单向数据流,与Angular等MVVM框架提倡的双向数据绑定观点相反。Flux认为双向数据绑定容易导致级联更新(cascading update),即对一个对象的更新会导致另一个对象被更新,并可能进一步触发更多的更新。当使用双向绑定的程序规模足够大之后,用户操作可能导致的哪些数据更新会很难预测。
前面提到过,Dispatcher是单例的,而且负责管理所有Flux数据流。每个Store都要向Dispatcher注册自身,并提供一个回调函数。Dispatcher会在接收到Action creator生成的Action之后调用所有回调,这样每个Store都会接受到Action。
要使用Facebook的Dispatcher实现,可以安装:
1 |
npm install flux --save |
然后在代码中获得其唯一实例:
1 |
var Dispatcher = require('flux').Dispatcher; |
Stores可以包含很多实例,其中包含了应用程序的状态和逻辑。其角色与MVC模式中的Model,但是Stores管理的是很多对象而不是单个。除了管理典型的数据实体之外,Stores还管理应用程序状态。
Store向Dispatcher注册自己的时候提供回调,该回调接收一个Action作为入参。在此回调中,通常具有一个基于Action类型的 switch 语句,在每个分支中会对Action进行简单处理,然后转调Store的内部方法完成数据更新。在更新完毕后,Store会广播一个事件,这样View就可以从事件中查询新状态并更新UI了。
在Flux应用程序中Store和View都是自我管理的,外部不能引起它们的状态变化。这种松耦合的设计很好的体现了迪米特法则——组件应该对系统的其它部分知道的尽可能的少。
Store可以声明自己需要等待另一个Store完成更新才能更新自己,Dispatcher则会根据Store之间的依赖关系,来决定执行回调的顺序。当Dispatcher的 dispatch() 方法被调用时,所有Store注册的回调被简单的、同步的迭代,以处理传递的事件。如果在某个回调中遇到 waitFor() 调用,则该回调会暂停执行,Dispatcher转而调用waitFor声明的、当前Store所依赖的其它Store的回调。只有所有依赖都执行完毕,先前暂停的回调才会恢复执行。示例回调代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function callback( action ) { switch ( action.type ) { case 'TODO_CREATE': // 等待以下两个Store的回调首先被执行 Dispatcher.waitFor( [ PrependedTextStore.dispatchToken, YetAnotherStore.dispatchToken ] ); // 然后从依赖的Store中取得数据 TodoStore.create( PrependedTextStore.getText() + ' ' + action.text ); break; } } |
如果waitFor()导致循环依赖,Dispatcher会抛出异常。
实现自己的Store时,可以参考以下最佳实践:
- 使用Store来缓存数据
- 暴露公共的Getter用于获取数据,但是绝对不要暴露公共的Setter
- 对Dispatcher派发的特定类型的Action作出响应
- 如果管理的数据/状态发生了变化,总是发布一个change事件
- 仅仅在处理派发的情况下才发布事件,即仅仅在注册的回调函数中发布事件
React通常作为Flux架构中的视图层,React组件就是View。位于View树的顶端的是一个特殊的View,它的职责不是UI渲染而是监听View树所依赖的Stores广播的事件,我们把这个特殊的View称为控制器视图(controller-view)。控制器视图提供从Store获取数据并将数据逐层下发到子代View的胶水代码。
当接收到事件后,视图控制器调用Store的公共Getter方法来获取新数据,然后调用自身的setState()或者forceUpdate()方法,导致View树的render()方法被调用而更新UI。
我们通常把Store的整个状态,作为一个对象,向View树中传递。这样子代View可以按需获取数据,并且减少需要传递的props的数量。
Dispatcher暴露了dispatch一个方法,我们可以调用此方法来传递一个附带了数据载荷(payload)的Action。但是View通常不会直接调用该方法,而是调用一个所谓Action creator,由后者调用Dispatcher。
Action creator是生成Action的工厂,它可以提供多个生成特定Action的方法。Action creator可以根据入参或者方法名决定要生成何种Action,并在生成后将Action转交给Dispatcher。
不论什么时候,也不管其来源是用户操作还是服务器的返回结果,只要新的数据进入到Flux程序中,它们就应该被包装到Action中并通过Dispatcher派发处理。
Action的类型由其type属性确定。Store接收到Action后,会依据其type来决定是否要对其作出响应。
本节展示一个Flux + React应用的简单示例,基于Fackbook官方的Flux实现。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import {Dispatcher} from 'flux' import {Container, ReduceStore} from 'flux/utils'; // 创建一个Dispatcher实例 var dispatcher = new Dispatcher(); // Action工厂 var Actions = { ADD: 'ADD', // 该Action表示将ValueStore的当前值+ins add: function ( ins ) { dispatcher.dispatch( { type: Actions.ADD, ins: ins } ) } } // 仅仅存储一个数值的Store class ValueStore extends ReduceStore { constructor( dispatcher ) { super( dispatcher ); } // 初始值 getInitialState() { return { value: 0 } } // 根据Action推导新值 reduce( state, action ) { switch ( action.type ) { case Actions.ADD: return { value: state.value + action.ins }; } } } // 实例化一个Store var valueStore = new ValueStore( dispatcher ); // 供Container包装的React组件 class AdderComponent extends React.Component { // 声明所依赖的Store static getStores() { return [ valueStore ]; } // 如果根据前一状态计算下一React组件状态 static calculateState( prevState ) { return valueStore.getState(); } constructor( props ) { super( props ); } handleClick = () => { Actions.add( 1 ); } render() { return ( <div> <button onClick={this.handleClick}>ADD ONE</button> <div>{this.state.value}</div> </div> ); } } const AdderContainer = Container.create( AdderComponent ); // Container也时React元素 ReactDOM.render( <AdderContainer/>, document.getElementById( 'root' ) ); |
本章内容介绍官方Flux实现的API。
register(function callback): string 由Store调用,向Dispatcher注册一个回调函数,当Dispatcher派发Action时,此回调函数会被执行 返回一个dispatchToken字符串,可以被waitFor()使用 |
unregister(string id): void 由Store调用,解除注册回调函数 |
waitFor(array<string> ids): void 由Store在回调函数体中调用,声明对其它Store的依赖关系。入参是dispatchToken的数组 |
dispatch(object payload): void 主要由Action creator调用,派发一个Action |
isDispatching(): boolean 提示Dispatcher是否正在执行派发 |
Flux提供了一系列基本的实用工具类,可以应对简单的应用程序。要使用Utils,可以导入:
1 2 |
// 或者导入其它类 import {ReduceStore} from 'flux/utils'; |
constructor(dispatcher: Dispatcher) 构造器,构造一个Store并且注册到Dispatcher |
addListener(callback: Function): {remove: Function} 为Store添加一个监听器,如果Store发生改变,监听器会被调用。返回值用于解除注册监听器 |
getDispatcher(): Dispatcher 返回关联的Dispatcher对象 |
getDispatchToken(): DispatchToken 返回Dispatcher识别当前Store的唯一记号,可以被waitFor()函数使用 |
hasChanged(): boolean 判断在当前派发过程中,此Store是否已经发生变化,只能在派发过程中使用 |
__emitChange(): void 发布一个事件,用于通知所有监听器当前Store已经变化,只能在派发过程中调用 |
__onDispatch(payload: Object): void 子类必须覆盖此方法,Store在此方法中接收到Dispatcher派发的事件并作出响应 |
该类继承自Store。使用该类时,你不需要手工的发布change事件。该类会自动根据reduce、areEqual的实现处理事件的发布:
getState(): T 暴露此Store的完整状态。如果你的状态(T)不是不可变对象,你应该覆盖此方法,避免直接暴露状态 |
getInitialState(): T 提供此Store的最初状态,该方法在Store初始化时被调用 |
reduce(state: T, action: Object): T 所有子类必须覆盖此方法,根据当前状态、Action推导出新状态作为返回值。该方法必须是pure函数且不能具有副作用 |
areEqual(one: T, two: T): boolean 检查两个对象是否相等,如果状态是不可变的则不需要覆盖此方法 |
该类继承自ReduceStore<Immutable.Map<K, V>> ,将状态定义为不可变的Map:
at(key: K): V 根据键来访问状态值,如果值不存在则抛出异常 |
has(key: K): boolean 判断指定的键是否存在 |
get(key: K): ?V 根据键来访问状态值,如果值不存在则返回undefiend |
getAll(keys: Iterable<K>, prev: ?Immutable.Map<K, V>): Immutable.Map<K, V> 依据指定的键集合keys,来获得对应的值。如果值存在,则将其存放到prev中 |
作为控制器视图的通用实现。Container包装一个普通的React组件,当此React组件关联的Store发生变化时,会自动调用其setState()方法。
要包装一个React组件,你需要调用其create方法:
create(base: ReactClass, options: ?Object): ReactClass 包装一个React组件类,返回一个Container options提供额外选项: |
Container包装的React组件必须实现以下静态方法:
static getStores(): Array 返回此React组件关联的Store |
static calculateState(prevState,props): Object 根据Store,重新计算此组件的状态对象 |
下面是使用Container的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import {Component} from 'react'; import {Container} from 'flux/utils'; class CounterContainer extends Component { static getStores() { // 依赖的Store return [ CounterStore ]; } static calculateState( prevState ) { return { // 根据Store重新计算组件状态 counter: CounterStore.getState(), }; } render() { // 使用状态 return <CounterUI counter={this.state.counter}/>; } } // 创建容器 const container = Container.create( CounterContainer ); |
Leave a Reply