Flux傻瓜教程

2015-08-02 01:42:48

看到一篇关于Flux入门的外文,通俗易懂,特此翻译。原文地址为http://blog.andrewray.me/flux-for-stupid-people/

翻译得挺辛苦,如果觉得读完有帮助,可以去给原文页面给原作者打赏,也算我借花献佛:)


Flux傻瓜教程

摘要:作为一个学弱,这篇文章就是我在学习Flux的路上挣扎的时候渴望见到的真诀。


一、什么时候应该用Flux

如果你的应用需要处理动态数据,那么没错,你或许需要用Flux

如果你的应用只是不需要共享状态的静态视图,并且不需要保存和更新数据,那还是算了吧,Flux不会带给你任何好处


二、Flux为何诞生?

这个问题迷惑了我一段时间,因为Flux是一套相对复杂的思想,为什么要增加复杂性呢?

90%应用都是包装了数据的UI对象。iOS toolkit定义了良好的views和data模型,极大地方便了应用开发。

——在前端,我们没有做到这点。相反我们面临一个大问题:没人知道应该怎样构建一个前端应用。我已经在这个行业里有些年头了,从没有学到什么“最佳实践”。。代替它的是,学到了一堆“库”- -!~ jQuery? Angular? Backbone?真正的问题,数据流,依然没人触及。


三、什么是Flux?

Flux这个词造出来,是为了描述事件和监听者的单项数据流。没有官方的Flux库,但是你需要Flux Dispatcher,和任意的JS 事件库。

官方文档写得太意识流了,那里不是一个开始的好地方,不过在你刚基础Flux的时候,它还是能够帮你填补一些空白。

不要试图比较Flux与MVC架构,那只能会让你变得困惑。

废话不说了,上干货。


1.视图派遣动作:Views “Dispatch” “Actions”

(译者注:view,action,dispatch,dispatcher等词,为了便于理解,以下将直接用原词)

Dispatcher必须是一个添加了额外规则的事件系统。它可以广播事件和存储回调函数。注意:只能有一个全局的分配器(“dispatcher”)。你应该用Facebook的Dispatcher库,它非常容易初始化:

var AppDispatcher = new Dispatcher();


比如,你的应用中有一个叫“new”的button,它可以将一个item添加到一个list钟。

<button onClick={ this.createNewItem }>New Item</button>

点击的时候会发生什么?你的view会dispatches一个特殊的action,带着action名字和新的item数据:

createNewItem: function( evt ){
AppDispatcher.dispatch({
actionName: ‘new-item’,//一是我们要做什么
newItem: { name: ‘Marco’} // 二是我们需要什么数据来做
 });
 }


“action”是Facebook创造的另外一个词。它是一个js对象,这个对象描述了两样东西,一是我们要做什么,二是我们需要什么数据来完成这个动作。就像上面的代码所示。


2.“Store”响应已发配的Actions

就像“Flux”、“action”一样,“store”也是由于Facebook原创的一个词。(译者注:感觉Facebook这套React还是挺拉风的,以前的的MVC,MVVM,都把model, view, controller三个词用烂了,Facebook直接全扔掉,创建一套新的,有一种自创宗派的气概,不论React之后发展得怎么样,这个装逼的姿势很正确:->)对于我们的应用,为了这里的list,我们需要一个包含逻辑和数据的集合,这些描述了我们的store。我们可以叫她ListStore。

一个Store是一个单例,这意味着你不应该用new来定义它。(译者注:直接不用new来创建一个对象就是单例!这个方法好,不用再像C++和Java那样自己code实现单例)。看代码:

var ListStore = {
items:[]
 }


你的store然后响应已经dispatch的Actions:

var ListStore = …
//Tell the dispatcher we want to listen 
// for any dispatched events
AppDispatcher.register(function( payload ){
switch(payload.actionName){
case ‘new-item’:
ListStore.items.push(payload.newItem);
break;
} 
} );


这就是正宗的Flux处理dispatch 回调函数的方法。每一个payload包含了一个action名字和data。一个switch语句决定了这个store是否对一个动作响应,并且在它能够响应action类型的时候马上改变数据。

这里有两个关键概念:

**store与model不一样,store包含了model

**store是应用中唯一能够知道如何改变数据的组件,是Flux最重要的部分。我们派遣的action并不知道如何增删数据。

如果应用中需要一个不同的部分去处理一些images和他们的元数据(metadata),你应该再创建一个store,起名为ImageStore。一个store代表应用中的一个单独的域(domain)。如果你的应用比较大,那么会有很多域;如果你的应用比较小,你可能只需要用一个store。总之,每一个model类型只能有一个store。

只有stores可以存储dispatcher callbacks!views不应该调用AppDispatcher.register.这里,Dispatcher只用来将数据从views发送到stores。views应该响应不同的事件。


3.Store发射“Change”事件

我们已经到这里了!现在你的数据已经改变了,我们需要让这个世界都知道它。

你的store发射一个事件,但是没用到dispatcher。这是值得困惑的,然而这就是Flux的方式。让我们赋予store这个能力:触发事件。如果你用的MicroEvent.js,这是简单的:

MicroEvent.mixin(ListStore);


然后让我们触发change事件:

case ’new-item’
ListStore.items.push( payload.newItem);
ListStore.trigger(‘change’);//触发change通知全世界
break;


关键点:在我们trigger的时候,我们并没有传送数据!我们的views只知道有些东西改变了,让我们跟着数据去看看为什么要这样做。


4.View 响应“Change”事件

现在我们需要呈现这个list。当list改变的时候view将会整个重绘(re-render)。

首先,让我们监听来自ListStore的的change事件,当组件”mounts”,即当组件第一次被创建。(译者注:只有先挂载成功,才能监听)

componentDidMount: function(){
ListStore.bind(‘change’, this.listChanged);
 }


为了简化,我们将调用forceUpdate,由它来触发重绘。

listChanged: function(){
//Since the list changed, trigger a new render.
this.forceUpdate();
 },


不要忘记清楚你的事件监听者,当你的组件卸载“unmounts”,即当她重回地狱的时候:

componentWillUnmount: function(){
ListStore.unbind(‘change’, this.listChanged);
 },


现在怎么样?让我们看一下绘制函数,我特意把它留在了最后:

render: function(){
var items = ListStore.getAll();
var itemHtml = items.map( function(listItem)){
 return <li key = {listItem.id}>
{ listItem.name}
</li>
});
return <div>
<ul>
{itemHtml}
</ul>
<button onClick = { this.createNewItem}>New Item</button>
</div>

 }

我们完成了整个循环。当你增加一个新的item的时候,这个view dispatches 一个action,这个store响应这个action,这个store改变数据,并且触发一个change event,然后view响应这个change event完成重绘。

但是这里还有一个问题:每次list改变的时候,我们都要重回整个view,这难道不是非常低效吗?

不。

当然,我们调用render函数。当然,所有在render函数中的代码将会重跑(re-run)。但是React只会在重绘输出改变的情况下才去刷新真正的DOM。你的render函数只是创造了一个虚拟DOM(vitual DOM)——React用来与之前的render输出相比较。如果两个虚拟DOM是不同的,React会重绘真正的DOM,并且只重绘不一样的地方!

关键概念:当store 数据改变的时候,你的view不关心事情是否改变。对,没错,它不管,它只管整体重绘!React的虚拟DOM比较算法承担了识别出真正DOM节点改变的重任。这使你的生命更加轻松,并且降低了你的血压。



另外:到底什么是一个“Action Creator”

记住,当我们点击button,我们发出了一个特定的action:

AppDispatcher.dispatch({
eventName: ‘new-item’,
newItem:{ name: ’Samantha'}
} ) ;


好吧,当很多view都需要dispatch这个action的时候这会导致极度的重复。另外,所有的view都需要知道特殊的对象格式。That’s lame!这太奢侈了!Flux提出一个抽象概念,称为“action creator”——它将上述动作抽象到一个函数中:

ListAction = {
add: function(item){
AppDispatcher.dispatche({
 eventName: ‘new-item’,
 newItem: item
 });
 }
 }


现在你的view只需要调用ListAction.add({name:’...'});并且不再需要关心要派遣的对象语法。


没有回答的问题

所有Flux告诉我们的是管理数据流。它并没有回答下列问题:

*你如何下载和存储数据到服务器?

*你如何管理非同父子模块间的通信?

*应该选用什么事件库,它会有什么影响吗?

*为什么Facebook不把这一切作为一个库一起发布?

*我应该用一个model层的框架比如Backbone作为我store中的model吗?

这些问题的答案是:have fun! 


PS: 不要用forceUpdate

我用forceUpdate是为了方便理解。对于你的组件来说,正确的方式是:copy data into state并且read from this.state in the render function。你可以在TodoMVC example中看到这个方法:https://github.com/facebook/flux/tree/master/examples/flux-todomvc

当组件第一次加载的时候,store data是拷贝到state的。当store更新的时候,这个data再全部重拷贝。这是更合适的,因为本质上,forceUpdate是同步的,而setState更加高效。


就这些了!


更多资源,从https://github.com/facebook/flux/tree/master/examples/flux-todomvc这里checkout吧。希望通过这篇文章,js/文件夹下的内容可以更容易理解。

在Flux的官方文档中有一些干货!不过它们被晦涩的写作手法深深埋葬。

作为一种Flux组织模块的例子,可以看ReactJS Controller View Pattern:

http://blog.andrewray.me/the-reactjs-controller-view-pattern/