看到一篇关于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/