前面几篇文章介绍了React相关的基本概念和运行原理,可以看到React是一个完全面向View的解决方案,它让我们能以一种新的思路去实现View,让很多复杂的场景可以用一种简单的方法去解决。然而在一个完整的应用程序中,除了实现View之外,我们还需要考虑如何同服务器通信、View之间如何交互以及View背后的数据模型如何去设计。那么Flux正是Facebook提出的解决这些问题的方案。
简单来说,Flux定义了一种单向数据流的方式,来实现View和Model之间的数据流动。它更像是一种设计模式而非一个正式的框架,以至于官方的Flux参考实现只有一个文件,区区100多行源代码。所以Flux继承了React的简单、直观的设计思想,让人一眼就能看明白其背后的运行原理。当然,要用好Flux,还是要正确理解其概念和背后的出发点,官方则是提供了两个具体的例子供大家参考。
Flux的标准实现非常简单,因此还衍生出了很多第三方实现,比较著名的包括Redux,Reflux,Fluxmm。而如今最为火热的应该属于Redux,它采用了函数式编程的思想来维护整个应用程序的状态。其实无论哪一种框架,都是以Flux的架构为基础而做的演变,其核心都是单向数据流和单向数据绑定。因而本文只会介绍官方的Flux,理解了标准实现之后也会更容易理解其他的实现方式。大家可以按照自己的兴趣和认可程度选择最适合自己的实现。
在传统MVC框架中,通常使用双向绑定的方式来将Model的数据展现到View。当Model中的数据发生变化时,一个或多个View会发生变化;当View接受了用户输入时,Model中的数据则会发生变化。在实际的应用中,当一个Model中的数据发生变化时,也有可能另一个相关的Model中的数据会被同步更新。这样,很容易出现的一个现象就是连锁更新(Cascading Update),Model可以更新Model,Model可以更新View,View也可以更新Model。你很难去推断一个界面的变化究竟是由哪个局部的功能代码引起。如下图所示, Model 和 View 之间的关系错综复杂,导致出现问题时很难调试;实现新功能时也需要时刻注意代码是否会产生副作用。
对此问题,Flux的解决方案是让数据流变成单向,引入Store、Action、Action Creators和Dispatcher等概念来管理信息流。如下图所示:
可以看到,数据流变成单向的。同时,数据如何被处理也被明确的定义了。在MVC中,数据如何处理通常由Controller来完成,在Controller中实现大部分的业务逻辑来处理数据。而现在则被清晰的定义在Store或者Action Creators中。当然,上图隐藏了一些细节,更为全面的架构图则如下所示:
在Flux中,View完全是Store的展现形式,Store的更新则完全由Action触发。得益于React的View每次更新都是整体刷新的思路,我们可以完全不必关心Store的变化细节,只需要监听Store的onChange事件,每次变化都触发View的re-render。从而也可以看到,尽管Flux架构可以离开React单独使用,但无疑两者结合是一个更加和谐的方案,能够各发挥所长。
为了对Flux有一个总体的印象,我们先考虑一个简单的使用场景:在文章评论页面提交一条评论。为此,我们需要向服务器发送一个请求提交新的评论,同时要将新的评论显示在列表中。这样的场景如果使用Flux去实现,大概需要实现以下几个部分:
整个架构如下图所示:
(点击放大图像)
整个流程的运行大概如下:
可以看到,无论请求成功和失败,都是去修改组件之外的Store,由Store通知UI进行变化。在这样一个架构中,Store中存储的是整个或者一部分应用程序的状态,React实现的View只需要监听Store的变化,而无需知道变化的细节,这也是由React组件的特点决定的。这样,我们就使用Flux完成了评论功能,不同于双向绑定,在Flux的流程中,数据如何流转和变化,变得非常清晰明确。虽然可能需要写更多的代码,但是带来了更清楚的架构。下面,我们来具体看其中的每个具体组件的概念和用法。
在Flux架构中,View即React的组件,而Store则存储的是应用程序的状态。在前面的文章中我们已经介绍过,React是完全面向View的解决方案,它提供了一种始终都是整体刷新的思路来构建界面。在React的思路中,UI就是一个状态机,每个确定的状态对应着一个确定的界面。对于一个小的组件,它的状态可能是在其内部进行维护;而对于多个组件组成的应用程序,如果某些状态需要在组件之间进行共享,则可以将这部分状态放到Store中进行维护。在Flux中,Store并不是一个复杂的机制,甚至Flux的官方实现中并没有任何Store相关的机制和接口,而是仅仅通过示例来描述了一个Store应该是什么样的数据结构。例如,在官方提供的TodoMVC例子( https://github.com/facebook/flux/tree/master/examples/flux-todomvc/ )中,Store的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span class="token keyword">var</span> _todos <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">var</span> TodoStore <span class="token operator">=</span> <span class="token function">assign</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> EventEmitter<span class="token punctuation">.</span>prototype<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * Get the entire collection of TODOs. * @return {object} */</span> getAll<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> _todos<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> emitChange<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">emit</span><span class="token punctuation">(</span>CHANGE_EVENT<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> addChangeListener<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span>CHANGE_EVENT<span class="token punctuation">,</span> callback<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> removeChangeListener<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">removeListener</span><span class="token punctuation">(</span>CHANGE_EVENT<span class="token punctuation">,</span> callback<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
可以看到,一个Flux的Store就是一个能触发onChange事件的对象,能够让其它对象订阅(addChangeListener)或者取消订阅(removeChangeListener)。同时,它提供了一些API供View来获取自己需要的状态。因此,也可以将Store理解为需要被不同View共享的公用状态。
那么,已经有了Store,React的组件(View)该如何使用它们呢?其实很简单,只需要在Store每次变化时都去获取一下最新的数据即可。我们可以看下TodoMVC中的实现:
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 |
<span class="token keyword">var</span> TodoStore <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../stores/TodoStore'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** * Retrieve the current TODO data from the TodoStore */</span> <span class="token keyword">function</span> <span class="token function">getTodoState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> allTodos<span class="token punctuation">:</span> TodoStore<span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> areAllComplete<span class="token punctuation">:</span> TodoStore<span class="token punctuation">.</span><span class="token function">areAllComplete</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> TodoApp <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createClass</span><span class="token punctuation">(</span><span class="token punctuation">{</span> getInitialState<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">getTodoState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> componentDidMount<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> TodoStore<span class="token punctuation">.</span><span class="token function">addChangeListener</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onChange<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> componentWillUnmount<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> TodoStore<span class="token punctuation">.</span><span class="token function">removeChangeListener</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onChange<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">/** * @return {object} */</span> render<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>Header <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>MainSection allTodos<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>allTodos<span class="token punctuation">}</span> areAllComplete<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>areAllComplete<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>Footer allTodos<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>allTodos<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">/** * Event handler for 'change' events coming from the TodoStore */</span> _onChange<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token function">getTodoState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
可以看到,在组件的componentDidMount方法中,开始监听Store的onChange事件,在componentWillUnmount方法中,取消监听onChange事件。在Store的每次变化后,都去重新获取自己需要的状态数据:getTodoState()。
通过这样一种很简单的机制,我们建立了从Store到View的数据绑定,每当Store发生变化,View也会进行相应的更新。那么底下我们需要关心当View接收用户交互,需要将新的状态存入到Store中,应该如何去实现。这就需要引入Flux的另外两个概念Dispatcher和Action。
顾名思义,Dispatcher就是负责分发不同的Action。在一个Flux应用中,只有一个中心的Dispatcher,所有的Action都通过它来分发。而Facebook的官方Flux实现其实就仅仅是提供了Dispatcher。使用Dispatcher只需要将其作为npm模块引入:
1 |
<span class="token keyword">var</span> Dispatcher <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'flux'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Dispatcher<span class="token punctuation">;</span> |
典型的,Dispatcher有两个方法:
这样,当View接受了一个用户的输入之后,通过Dispatcher来分发一个特定的Action,而对应的Action处理函数会负责去更新Store。这个流程在文章开始的图中可以清楚的看到。因此,通常来说Action的处理函数会和Store放在一起,因为Store的更新都是由Action处理函数来完成的。例如在TodoMVC中,TodoStore中会处理如下Action:
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 |
Dispatcher<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> text<span class="token punctuation">;</span> <span class="token keyword">switch</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>actionType<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_CREATE<span class="token punctuation">:</span> text <span class="token operator">=</span> action<span class="token punctuation">.</span>text<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>text <span class="token operator">!==</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">create</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_TOGGLE_COMPLETE_ALL<span class="token punctuation">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>TodoStore<span class="token punctuation">.</span><span class="token function">areAllComplete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">updateAll</span><span class="token punctuation">(</span><span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">updateAll</span><span class="token punctuation">(</span><span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_UNDO_COMPLETE<span class="token punctuation">:</span> <span class="token function">update</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_COMPLETE<span class="token punctuation">:</span> <span class="token function">update</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_UPDATE_TEXT<span class="token punctuation">:</span> text <span class="token operator">=</span> action<span class="token punctuation">.</span>text<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>text <span class="token operator">!==</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">update</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token punctuation">{</span>text<span class="token punctuation">:</span> text<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_DESTROY<span class="token punctuation">:</span> <span class="token function">destroy</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_DESTROY_COMPLETED<span class="token punctuation">:</span> <span class="token function">destroyCompleted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// no op</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
无论是添加、删除还是修改一个Todo项,都是由Action来触发的。在Action处理函数中,不仅对Store进行了更新,还触发了Store的onChange事件,从而让所有监听组件能够得到通知。完整的代码可以参考:https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/stores/TodoStore.js 。
通过Dispatcher和Action,实现了从View到Store的数据流,进而实现了整个Flux的单向数据流循环。从这里可以看到,Dispatcher是全局唯一的,相当于是所有Action的总hub,而每个Action处理函数都能够收到所有的Action,至于需要对哪些进行处理,则由处理函数自己决定。例子中是通过switch来判断Action的type属性来决定如何进行处理。因此,虽然不是必须,但是一般Action都会有一个type属性来标识其类型。
有了上述概念和机制,基本上就已经有了Flux的整个架构的模型。那么Action Creators又是什么呢?顾名思义,Action Creators即Action的创建者。它们负责去创建具体的Action。一个Action可以由一个界面操作产生,也可以由一个Ajax请求的返回结果产生。为了让这部分逻辑更加清晰,让View更少的去关心数据流的细节,于是有了Action Creators。例如,对于一个TodoItem组件,当用户点击其中的Checkbox时,会产生一个COMPLETE_TODO的Action,直观的看,完全可以在View的内部去实现Dispatcher.dispatch({type: ‘COMPLETE_TODO’, payload: {…}),而为了保持View的简单和直观,通常会在独立的Action Creators去封装这部分逻辑,例如:
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 |
<span class="token keyword">var</span> AppDispatcher <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../dispatcher/AppDispatcher'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> TodoConstants <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../constants/TodoConstants'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> TodoActions <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * @param {string} text */</span> create<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span> <span class="token punctuation">{</span> AppDispatcher<span class="token punctuation">.</span><span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> actionType<span class="token punctuation">:</span> TodoConstants<span class="token punctuation">.</span>TODO_CREATE<span class="token punctuation">,</span> text<span class="token punctuation">:</span> text <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">/** * @param {string} id The ID of the ToDo item * @param {string} text */</span> updateText<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> text<span class="token punctuation">)</span> <span class="token punctuation">{</span> AppDispatcher<span class="token punctuation">.</span><span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> actionType<span class="token punctuation">:</span> TodoConstants<span class="token punctuation">.</span>TODO_UPDATE_TEXT<span class="token punctuation">,</span> id<span class="token punctuation">:</span> id<span class="token punctuation">,</span> text<span class="token punctuation">:</span> text <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> … <span class="token punctuation">}</span> |
这里的TodoActions就是一个Action Creator,它把具体分发Action的动作封装成具有语义的方法,供View去使用,那么在一个TodoItem的组件中,其界面JSX可能就是:
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 |
… render<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> todo <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>todo<span class="token punctuation">;</span> <span class="token keyword">var</span> input<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>isEditing<span class="token punctuation">)</span> <span class="token punctuation">{</span> input <span class="token operator">=</span> <span class="token operator"><</span>TodoTextInput className<span class="token operator">=</span><span class="token string">"edit"</span> onSave<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onSave<span class="token punctuation">}</span> value<span class="token operator">=</span><span class="token punctuation">{</span>todo<span class="token punctuation">.</span>text<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>li className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token function">classNames</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token string">'completed'</span><span class="token punctuation">:</span> todo<span class="token punctuation">.</span>complete<span class="token punctuation">,</span> <span class="token string">'editing'</span><span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>isEditing <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span> key<span class="token operator">=</span><span class="token punctuation">{</span>todo<span class="token punctuation">.</span>id<span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"view"</span><span class="token operator">></span> <span class="token operator"><</span>input className<span class="token operator">=</span><span class="token string">"toggle"</span> type<span class="token operator">=</span><span class="token string">"checkbox"</span> checked<span class="token operator">=</span><span class="token punctuation">{</span>todo<span class="token punctuation">.</span>complete<span class="token punctuation">}</span> onChange<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onToggleComplete<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>label onDoubleClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onDoubleClick<span class="token punctuation">}</span><span class="token operator">></span> <span class="token punctuation">{</span>todo<span class="token punctuation">.</span>text<span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>label<span class="token operator">></span> <span class="token operator"><</span>button className<span class="token operator">=</span><span class="token string">"destroy"</span> onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onDestroyClick<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">{</span>input<span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>li<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> _onToggleComplete<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> TodoActions<span class="token punctuation">.</span><span class="token function">toggleComplete</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>todo<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> … |
通过将Action的创建逻辑放到Action Creators,可以让View更加简单和纯粹,View不需要知道背后是否有Flux,而只需要知道调用某个方法来实现某个功能,从而让View的开发更加流畅和直观 。完整代码可以参考:https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/components/TodoItem.react.js 。
本文介绍了Facebook提出的面向React的一种新的应用架构模式Flux,这种架构也已经在Facebook内部被广泛使用,其概念和原理虽然很简单直观,但是确实被证明有能力去组织一个完整的大型应用。然而Flux的中心Dispatcher的模式,以及Action作为全局可知的数据流的方式,仍然有一些争论。因此,社区中也是出现了非常多的类Flux实现,如今最成熟的莫过于Redux,其最大的特点在于Action能够分层次的去负责一个全局Store的不同部分,从而更容易去模块化应用状态的管理。理解了本文介绍的Flux概念,对于理解Redux也会有很大的帮助。