社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
转载出处:http://www.cnblogs.com/lovesueee/p/4893218.html
Flux作为一种应用架构(application architecture)或是设计模式(pattern),阐述的是
单向数据流
(a unidirectional data flow)的思想,并不是一个框架(framework)或者库(library)。
在细说Flux
之前,还是得提一下React ,毕竟Flux这个名字,是因为它才逐渐进入到大众视野。
React
是facebook提出来的一个库,用来构建用户界面(U
ser I
nterface),它的三大特点(来自官方):
JUST THE UI
: 仅仅是一个View(components),可以认为是MVC中V
,用来构建UI界面。VIRTUAL DOM
: 虚拟dom,为的是:高性能dom渲染(利用diff算法)、组件化(向web components看齐)、多端同构(node,react native)。DATA FLOW
: 单向数据流(one-way data flow),指的是:一种自上而下的渲染方式(top-down rendering)。 总而言之,对于一个react web应用,它的UI将会由无数个组件(react component)嵌套组合
而成,它们之间存在着层级(hierarchy)关系(通过JSX的语法糖可以轻易看出),也就因此有了父组件,子组件和顶层组件的概念。
然而就像上述第一点所说,React仅仅是一个View,对于一个web应用,没有数据就显得毫无意义。
现在,假使我们通过一个WebAPI模块取得了数据,那么如何传递给React 组件(components),从而实现UI渲染呢?结合组件的层级关系,想到上述所说的第三点:自上而下的渲染
,我们将数据传递给顶层组件
(controller-view),同样作为父组件的它,便可以通过组件的属性(properties)将一些有用数据传递给它的各个子组件(各取所需数据),就这样一级一级自上而下地传递下去(直到每一个叶子组件),最终,每一个组件都将得到自己渲染所需要的数据,从而完成UI的渲染。
那么,倘若此时数据变化了(比如:对于一个列表而言,用户点击删除按钮,删除了一条数据),我们又该如何通知各个组件进行UI更新呢?
有这样一种清晰的思路:
最顶层组件
,重新完成一次自上而下的渲染
(re-render),从而更新了UI(不要过分担心性能问题,VIRTUAL DOM
就是用来解决这个的)。显然上述的几步,React作为一个View是不可能做到的,也正因为这样,Flux作为一种架构方案才被提出来,它的思想大体就是上述这几步,通过一个单向数据的流动,完成了UI的更新,用一张图可以表示,如下(以Facebook Flux为例):
当然,作为应用数据处理的模式,除了Flux,还有很多(如:传统的MVC,MVVM),只是Flux凭借其单向数据流
特点,使得数据流变得简单,易于调试和追踪问题,所以更适合与React进行组合使用。
前面,我们就一直在说,Flux是一种架构,一种模式,并不是一个框架,也不是一个库,就像我们说MVC(VM)的概念一样,所以,遵循着Flux模式所阐述的思想自然就会出现一些库,如:Facebook Flux
、Reflux
、Fluxxor
、Redux
等等。
本文主要讲解的Reflux,不过在这之前还是需要先提一下Facebook Flux,从而为后面一些对比做一些铺垫。
Facebook Flux,是Facebook在提出Flux架构后,给出的一个对Flux的简单实现,可以认为是Flux库的第一个范例,所以,也有人称之为Original Flux
。
Facebook Flux中引入了四个概念: Dispatcher
、 Actions
、Stores
、Views(Controller-Views)
,而它们之间的关系就如同上面的那张图所描述的一样,构成了一个单向数据流的闭环,简化版如下:
接下来,将以官方的TodoMVC Demo为例,来说明它们各自的作用,以及它们之间是如何配合工作的?(PS:建议读者将源代码clone下来,边看边调试)
Facebook Flux中所指的Views,其实就是React Components
,用作UI渲染,而相对特别的,Controller-Views
指的则是顶层React Component
,除了UI渲染外,它还负责接收来自Store变化的数据,并传递给它的Child Component(即Controller-View -> Child Views),用于子View的渲染。
在这个例子中,TodoApp就是一个Controller-View,它监听到TodoStore的数据变化后,便会重新从TodoStore中获取数据,然后通过调用组件setState()
方法,触发render()
方法的执行,从而得到UI的更新(自上而下的渲染)。
// 从TodoStore中获取数据
function getTodoState() {
return {
allTodos: TodoStore.getAll(),
areAllComplete: TodoStore.areAllComplete()
};
}
var TodoApp = React.createClass({
componentDidMount: function() {
// TodoApp监听TodoStore的数据变化
TodoStore.addChangeListener(this._onChange);
},
render: function() {
return (
<div>{/* 此处代码省去 */}</div>
);
},
_onChange: function() {
// 重新获取TodoStore的数据,并通过调用setState,触发re-render
this.setState(getTodoState());
}
});
Facebook Flux中的Stores,作为数据存储的模块,类似于MVC中的Model,它负责接收Dispatcher分发过来的actions,针对不同的actionType,对数据就进行不同的操作(如:增删改查),最后再通知View,数据变化了,需要进行UI更新。
在这个例子中,TodoStore通过变量_todos变量存储着整个应用的数据(一个列表),并通过AppDispatcher(Dispatcher实例)注册回调,来接收不同类型的Action指令,进而执行不同的数据操作(mutate data),最后通知TodoApp View数据改变,需要更新UI(re-render)。
// 数据存储(一个列表)
var _todos = {};
// 操作数据的函数
function create(text) {/*此处代码省去*/}
function update(id, updates) {/*此处代码省去*/}
function destroy(id) {
delete _todos[id];
}
// 接收分发过来的Action
AppDispatcher.register(function(action) {
var text;
// 判断Action类型,采取不同的数据操作
switch(action.actionType) {
// 新增
case TodoConstants.TODO_CREATE:
text = action.text.trim();
if (text !== '') {
create(text); // 创建数据,并存储
TodoStore.emitChange(); // 通知TodoApp数据变化,需要更新UI
}
break;
// 更新
case TodoConstants.TODO_UPDATE_TEXT:/*此处代码省去*/
break;
// 删除
case TodoConstants.TODO_DESTROY:/*此处代码省去*/
break;
/*此处省去部分代码*/
}
});
Facebook Flux中,Dispatcher起到了一个中央枢纽(Central Hub)的角色,它存储着一张Stores列表清单,并且负责Actions的分发工作,即Action的一旦触发,Dispatcher将会通知列表清单上的所有的Stores,每一个Store则选择性地针对该Action进行特定处理(或者不处理)。
在一个应用中,Dispatcher实例只允许有一个(Single),也就是说它将作为一个单例而存在。
在这个例子中,AppDispatcher就是这样一个单例,我们在TodoStores通过AppDispatcher.register()
注册回调(见上段代码),来接收不同类型的Actions(消息订阅),在TodoActions里通过AppDispatcher.dispatch()
执行不同Actions的分发(消息发布),如下:
var TodoActions = {
// 新增Action
create: function(text) {
AppDispatcher.dispatch({ // 通知TodoStore对数据进行修改(带有Action类型和关联数据)
actionType: TodoConstants.TODO_CREATE, // Action类型:create
text: text // 传递给TodoStore的数据
});
},
// 更新Action
updateText: function(id, text) {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_UPDATE_TEXT, // Action类型:update
id: id,
text: text
});
},
// 删除Action
destroy: function(id) {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_DESTROY, // Action类型:destroy
id: id
});
}
/*此处省去部分代码*/
};
Facebook Flux中的有一个概念叫做Action Creator
,可以将它理解为一个方法(即helper method
),专门用来创建某种类型的Action。
上一段代码中,TodoActions模块就提供了这些helper methods(或者叫做Action Creators),如:
上述每一个方法在内部,都定义了自己的常量类型(actionType),并且将接收的参数作为数据(payload),从而封装成一个完整的Action(即actionType + payload = Action
)。
最后,再统一通过调用Dispatcher.dispatch()
将特定的Action以消息的形式分发出去(即传递给Stores),Stores在得到Action后,便可以通过Action.actionType
来判定采取某种操作(或者忽略这个Action),而执行操作时需要用到的数据则来自Action.payload
。
Facebook Flux中提出的这四个概念,承担着各自角色,通过互相协作,形成了一个单向数据流的闭环。
------【推荐大家看下这篇文章《A cartoon guide to Flux》,生动形象地描述了这几个角色。】
说完了Facebook Flux,让我们静静思考一下,存在的不足:
倘若,有一个单页面应用,程序中就可能存在N个store,每个store都会监听1~N个action,代码就会像这样:
// storeA.js
Dispatcher.register(function (action) {
switch(action.actionType) {
case 'actionA': break;
case 'actionB': break;
/* ... 1~N个action */
case 'actionN': break;
}
});
// storeB.js
Dispatcher.register(function () {
// 同上
});
/* ... */
/* ... 1~N个store */
/* ... */
// storeN.js
Dispatcher.register(function () {
// 同上
});
假使此时,触发了一个actionX,那么storeA~storeB的通过Dispatcher.register()
注册的回调函数会按注册顺序依次被触发(无一例外),也就是说每个store都会得到actionX通知,唯一不同的可能就是:每个store模块,会通过各自的switch语句
进行判断,有的对actionX做处理,有的则不处理(忽略),那么问题来了:
『既然有些store对actionX不需要处理,那么它们注册的回调执行是否有必要?毕竟是函数执行是有开销的,如果有1000个store对actionX不"感冒"的的话,会不会很浪费资源?』
分析下这个问题:Facebook Flux是以Dispatcher
(发布者)作为消息中枢,所有的Action消息都会统一从这里分发出去,广播给所有的Store(订阅者),也就是说:发布者(Dispatcher)和订阅者(Stores)之间存在着一对多的关系
,而事实上Actions(消息)和Stores(订阅者)之间却存在着一个多对多的关系
,如下图:
这样的矛盾,就使得,每一个Store不得不在自己的回调函数里通过Switch语句
,来判断当前Action的类型,来决定要不要进行处理,那么暂且抛开性能不说,显然,这样写法,却显得繁重且不够优雅。
于是,接下来,看看Reflux在Facebook Flux的基础之上,做了那些优化?
Reflux,是另一个实现Flux模式的库,旨在使整个应用架构变得更加简单。
准确地说,Reflux是由Facebook Flux演变而来(inspired by Facebook Flux),可以说是它的一个进化版本,自然而言就会拿两者进行比较:详见这里。
简要概括一下重点,就是:
1.Reflux保留了Facebook Flux中原有的三个概念:Actions
、Stores
、Views(Controller-Views)
,去除了Dispatcher
,如果要用一张图表示的话,就是这样:
此时会有人问:没有了消息中枢(Dispatcher),消息Actions如何发布出去,并传递到Stores呢?
答:在Reflux中,每一个Action本身就是一个Publisher
(消息发布者),即自带了消息发布功能;而每一个Store除了作为数据存储之外,它还是一个Subscriber
,或者叫做Listener
(消息订阅者),自然就可以通过监听Action,来获取到变化的数据。
2.Store之间可以互相监听
这样的场景还是有的,比如:在单页面应用中,如果不同Page拥有不同的Store,那么就可能会出现:子页面Store数据变化后,需要通知到父页面Store进行相应修改的情况。
回顾上一节中,对于Facebook Flux的思考,所遗留的问题点,在Reflux中是否解决了呢?
答案是:肯定的。
这里先简单说明下:
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u012677935/article/details/51917133
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!