本文转载自:众成翻译 译者:净化 链接:http://www.zcfy.cc/article/2469 原文:http://blog.andrewray.me/flux-for-stupid-people/
作为一个小白,Flux很不直观,也没有很多入门级文档,没有明确的解释。在我摸索学习 Flux 的时候,有人能告诉我下面这些东西。
本文是写给小白ReactJS教程的续篇。
如果你的应用使用了 动态数据 那么 你应该用Flux。
如果你的页面只是 静态页面 不共享状态,你不需要保存和上传数据,你不需要用Flux,Flux不会给你带来好处。
幽默的说,因为Flux是一个相对难懂,复杂的概念。为什么要增加复杂度呢?
90% 的IOS应用把 数据传入table view IOS封装了很好的视图组件,数据模型,让开发者轻松的开发。
在前端开发中(HTML, Javascript, CSS),我们没有这样的概念。也就是说我们要面对一个大问题: 我们不知道该如何构建一个前端应用程序。我入行多年,没有人探讨“最佳实践”。反而是探讨类库,比如jQuery,Angular,Backbone,但是我们逃避了最重要的问题,数据流。
Flux 是一个名词,主要描述一种用特别的事件和监听器处理数据流的方式。这里没有官方的Flux类库,但是你需要用到 Flux Dispatcher和event library。
官方文档写的好像某个人脑子里的意识流,这并不是一个适合作为初学者起步的文档,只有当深入了解Flux时,才能理解作者,脑补作者没写的内容。
不要试图比较 Flux 和 MVC 架构,这只会让你更加混淆。
让我们开始吧,这次我将 逐个解释概念。
dispatcher本质是一个添加了额外规则的事件系统。它分发事件并定义回调函数。 只能定义一个全局的dispatcher。你可以使用Facebook的类库 Dispatcher Library。举个例子:
`var AppDispatcher = new Dispatcher();`
比方说有一个“新建”按钮,点击按钮把item添加到一个列表中。
`<button onClick={ this.createNewItem }>New Item</button>`
点击后会发生什么?View 层触发一个特定事件,包括item名和item的数据:
createNewItem: function( evt ) {
AppDispatcher.dispatch({
actionName: 'new-item',
newItem: { name: 'Marco' } // example data
});
}
"action" 是Facebook创造的另一个词。action一个Javascript对象,表示我们想做的动作,和我们需要的数据。如上所述,我们想创造一个new-item
,我们需要的数据是 name
。
Flux中"store" 也是一个Facebook创造的名词。我们的应用中,我们需要一个集合存放状态,我们叫它ListStore。
Store 就是一个单例,意味着你不需要 new 出来。ListStore 就是一个全局的对象:
// Single object representing list data and logic
var ListStore = {
// Actual collection of model data
items: []
};
然后 Store 响应分发出来的事件:
var ListStore = …
// Tell the dispatcher we want to listen for *any*
// dispatched events
AppDispatcher.register( function( payload ) {
switch( payload.actionName ) {
// Do we know how to handle this action?
case 'new-item':
// We get to mutate data!
ListStore.items.push( payload.newItem );
break;
}
});
这就是传统的Flux处理分发事件回调函数的方式。payload 包含一个事件名和数据。使用 switch 语句来决定调用哪个操作,是响应回调,还是改变数据,还是其他。
核心观点: Store 不是 model,而是 model 的容器。
核心观点: 应用中唯一知道如何更新数据的就是 Store。这是 Flux 最重要的一部分。我们分发的事件是不知如何添加或者删除条item。
再比如,应用的其他部分需要保存图片和它们的元数据,你需要使用另外一个 Store,可以取名为 ImageStore。一个 Store 就代表了应用的一个单独的域。如果应用足够大,那么下呢按划分区域。如果应用不大,那很可能只需要一个 Store。
只有 Store 可以注册 callback。View 永远都不应该调用 AppDispatcher.register。Dispatcher 的存在就是为了把消息从 View 传递到 Store。而 View 则是响应另外一种事件。
现在数据已经变化了,我们要通知其他部分。
Store 触发一个事件,但不使用 Dispatcher。这也许会让你混淆,不过这就是 Flux 的方式。下面我们给Store添加event,如果你使用 MicroEvent.js ,可以这么写:
`MicroEvent.mixin( ListStore );`
接下来就是触发事件:
case 'new-item':
ListStore.items.push( payload.newItem );
// Tell the world we changed!
ListStore.trigger( 'change' );
break;
核心观点: 就单单触发事件,不传递最新的item。View 只关心是不是有内容改变了。下面我会说一下原因。
现在我们需要展示列表了,当列表变化的时候,View会完全重绘。
首先,当 Component “上马”的时候,监听 ListStore 的 change 事件,就是在 Component 创建时:
componentDidMount: function() {
ListStore.bind( 'change', this.listChanged );
},
简单点,直接调用 forceUpdate,触发重绘。另外一种方式,就是把整个列表保存在 state 中。
listChanged: function() {
// Since the list changed, trigger a new render.
this.forceUpdate();
},
当 Component加载完时,记得清除监听函数,也就是在 Component 调取回调函数的时候:
componentWillUnmount: function() {
ListStore.unbind( 'change', this.listChanged );
},
接下来该做什么?看看我们的Render函数,我有意把它放到最后:
render: function() {
// Remember, ListStore is global!
// There's no need to pass it around
var items = ListStore.getAll();
// Build list items markup by looping
// over the entire list
var itemHtml = items.map( function( listItem ) {
// "key" is important, should be a unique
// identifier for each list item
return <li key={ listItem.id }>
{ listItem.name }
</li>;
});
return <div>
<ul>
{ itemHtml }
</ul>
<button onClick={ this.createNewItem }>New Item</button>
</div>;
}
整个流程就是这样。当你添加以一个item,View item触发一个行为,Store 对这个行为作出相应,Store 更新,Store 触发 change 事件,接着 View 响应这个 change 事件重绘。
但是有一个问题:每次列表变化的时候都重绘整个 View,那样效率低的可怕。
不是。
没错,render 函数确实被调用了,render 函数中所有代码都一次次的执行了。但是只有在 DOM 真正变化的时候 React 才把变化 render 出来 。render
函数生成了一个虚拟DOM,React 会对比之前一次 render 出来的 DOM。如果这两次的虚拟DOM不一样,React 就更新真实 DOM 中变化的部分。
核心观点:当 Store 变化时,View 无需关心条目是添加、删除,还是修改了。它只需要整个重绘,React 的“虚拟DOM”算法进行复杂的运算,找出哪些真实的DOM节点变化了。这可以帮助你简单生活,不再紧张。
你应该还记得,点击按钮,触发事件:
AppDispatcher.dispatch({
eventName: 'new-item',
newItem: { name: 'Samantha' }
});
不过,如果 View 中有很多地方都需要触发这个事件,这就太冗余了。而且,所有的 View 都需要知道事件对象的特定格式。这样有些别扭。Flux 提出一个抽象的层,叫做行为action creators(创建器),其实就是把上面的代码放到一个函数中。
ListActions = {
add: function( item ) {
AppDispatcher.dispatch({
eventName: 'new-item',
newItem: item
});
}
};
现在 View 只需要调用ListActions.add({ name: '...' });
,不用关心分发对象的语法了。
所有关于Flux的文章,大多告诉我们如何管理数据流,但是并没有解答如下问题:
如何加载,保存数据并更新到服务端?
在不用父组件的情况下,组件之间如何通信?
我应当使用哪个events library?这是否很重要?
为什么Facebook没有把Flux相关的类库打包成一个类库?
我是否可以使用一个数据层代替store,比如Backbone的model?
这些问题的答案是: 开心就好!
我使用了 强制更新
的目的仅仅是为了简单举例。正确的更新视图方法是,读store里的数据, 复制数据到state通过读this.state
再调用render
函数更新。你可以看这个例子。 TodoMVC example.
当模板第一次加载时, store数据被复制 到state
. 当store更新, 数据再次被完全复制。这样会更好,因为它只在内部, 强制更新
是同步的,通过setState
效率更高。
作为附加资源,可以查看Facebook提供的Example Flux Application 。希望看过这个 the files in the js/
folder will be easier to understand.
Flux文档包含一些高级技巧,还有一些深层的内容。
还有一个关于Flux的例子,参照ReactJS Controller View Pattern.
如果这篇文章帮助你理解了Flux, 可以考虑关注我Twitter 或 buying me a coffee :)
扫码关注w3ctech微信公众号
共收到0条回复