从MVC到Flux
一、 MVC框架
把应用分为三个部分:
- Model(模型):负责管理数据
- View(视图):负责渲染用户界面
- Controller(控制器):赋值接受用户输入,根据用户输入调用对应的Model部分逻辑,把产生的数据结果交给View部分呢,让View渲染出必要的输出。
缺点:无法禁绝View和Model之间的直接对话。
二、Flux框架
优点:
通过设置规则,即如果要改变界面,必须改变Store中的状态,如果要改变Store中的状态,必须派发一个action对象,规则禁绝了数据流混乱的可能。
Flux与MVC的对应关系:(左:flux,右:MVC)
view =》 view
dispatcher =》 controller
store =》 model
action =》 请求
缺点:
1、Store之间依赖关系。
当两个Store之间有逻辑依赖关系,就必须用上Dispatcher的waitFor函数。
另外,每一个Store是通过register函数的返回值dispatchToken标志的。
某些Store必须要等待其他Store处理完action后,才能进行操作。
2、难以进行服务器渲染
facebook设计flux时,就不是用作服务器端渲染的。
3、Store混杂了逻辑和状态。
做不到动态替换一个Store的逻辑。但是Redux可以做到。
三、Flux框架实例应用(修改前面的案例为flux框架应用)
首先,需要使用npm安装一下flux,此处不赘述了。
- 第一步:创建Dispathcer类,作用:用于派发action。
实例化一个dispatcher实例,进行action分发操作、store注册操作等。
import {Dispatcher} from 'flux';
export default new Dispatcher();
- 第二步:action配置,通常包括两小步:
- 首先,定义ActionTypes.js文件,用于定义对象的多种类型type,通常为字符串。
- 其次,定义Actions.js文件,用于定义action的多个构造函数,在构造函数内部,会通过dispatcher实例对象提供的dispatch函数派发出去。
【注意:此文件中,每一个构造函数并不是action对象本身,而是能够产生并派发action对象的函数。】
/*ActionTypes.js文件内容*/
export const INCREMENT ='increment';
export const DECREMENT ='decrement';
/*Actions.js文件内容*/
/*当文件有多个使用export导出时,可以用此语法将所有导出融为一个对象进行访问。*/
import * as ActionTypes from './ActionTypes.js';
import AppDispatcher from './AppDispatcher.js';
export const increment =(counterCaption)=>{
AppDispatcher.dispatch({
type:ActionTypes.INCREMENT,
counterCaption:counterCaption
});
};
export const decrement=(counterCaption)=>{
AppDispatcher.dispatch({
type:ActionTypes.DECREMENT,
counterCaption:counterCaption
});
};
- 第三步:定义不同组件的不同store对象,用于存储应用状态,同时还要接受Dispatcher派发的动作,根据动作来决定是否要更新应用状态。
Tips:由于store会有多个,所以单独建立了一个stores文件夹进行存储为好。
注意:虽然名为store,但并不表示一个Store必须要存储,Store只是提供获取数据的方法,二Store提供的数据完全可以另一个Store计算得来。
/*CounterStore.js文件内容 */
import AppDispatcher from '../AppDispatcher';
import * as ActionTypes from '../ActionTypes';
const counterValues={
'First':0,
'Second':10,
'Third':30
};
// 1、使用消息的方式建立Store和View的联系,相当于CounterStore成了EventEmitter实例对象。支持了emit、on、removeListener函数。
const CounterStore=Object.assign({},EventEmitter.prototype,{
getCounterValues:function(){
return counterValues;
},
emitChange:function(){
this.emit(CHANGE_EVENT);
},
addChangeListener:function(callback){
this.on(CHANGE_EVENT,callback);
},
removeChangeListener:function(callback){
this.removeListener(CHANGE_EVENT,callback);
}
});
// 2、store只有注册到Dispatcher实例上才能真正发挥作用
// 3、回调函数 作用:根据action对象来决定如何更新自己的状态
// 其中的形参action:是派发给dispatcher的action对象(第三步中的操作)
CounterStore.dispatchToken=AppDispatcher.register((action)=>{
if(action.type===ActionTypes.INCREMENT){
counterValues[action.counterCaption]++;
CounterStore.emitChange();
}else if(action.type===ActionTypes.DECREMENT){
counterValues[action.counterCaption]--;
CounterStore.emitChange();
}
});
export default CounterStore;
展示总和的组件Summary单独建立自己的store文件,SummaryStore.js。
import AppDispatcher from '../AppDispatcher';
import * as ActionTypes from '../ActionTypes';
import CounterStore from './CounterStore';
function computeSummary(counterValues){
let summary=0;
for(const key in counterValues){
if(counterValues.hasOwnProperty(key)){
summary+=counterValues[key];
}
}
return summary;
}
const SummaryStore=Object.assign({},EventEmitter.prototype,{
getSummary:function(){
// 实时读取CounterStore里面的值
return computeSummary(CounterStore.getCounterValues());
},
emitChange:function(){
this.emit(CHANGE_EVENT);
},
addChangeListener:function(callback){
this.on(CHANGE_EVENT,callback);
},
removeChangeListener:function(callback){
this.removeListener(CHANGE_EVENT,callback);
}
});
SummaryStore.dispatchToken=AppDispatcher.register((action)=>{
if((action.type===ActionTypes.INCREMENT)||(action.type===ActionTypes.DECREMENT)){
//dispatchToken的用处:
// waitFor函数:告诉Dispatcher,当前的处理必须要暂停,直到dispatchToken代表的那些已注册回调函数执行结束才能继续。
AppDispatcher.waitFor([CounterStore.dispatchToken]);
SummaryStore.emitChange();
}
});
- 第四步:配置Counter.js组件文件内容(相当于view视图的配置)
Tips:会有多个视图(组件),所以建议建立view文件夹进行存储。
import React,{Component} from 'react';
// 注意:react最新版本已经将proptypes独立出来,需要自行安装prop-types才可以使用。
import PropTypes from 'prop-types';
import CounterStore from '../stores/CounterStore';
import * as Actions from '../Actions';
export default class Counter extends Component{
constructor(props){
super(props);
this.onJia=this.onJia.bind(this);
this.onJian=this.onJian.bind(this);
this.state={
// 实时获取到存储的值
counter:CounterStore.getCounterValues()[props.caption]
}
}
componentDidMount(){
// 在装载完成后,给store添加事件
CounterStore.addChangeListener(this.onChange);
}
componentWillUnmount(){
// 在卸载前,删除store事件
CounterStore.removeChangeListener(this.onChange);
}
// 定义事件函数
onChange(){
// 实时从store中获取到新值 赋值给count
const newCount=CounterStore.getCounterValues()[this.props.caption];
this.setState({count:newCount});
}
onJia(){
// this.updateCount(true);
// 分发一个action
Actions.increment(this.props.caption);
}
onJian(){
// this.updateCount(false);
// 分发一个action
Actions.decrement(this.props.caption);
}
render(){
const btnStyle={
margin:"0 20px"
};
return (
<div>
<button onClick={this.onJia} style={btnStyle}>+</button>
<span>{this.props.caption}:{this.state.counter}</span>
<button onClick={this.onJian} style={btnStyle}>-</button>
</div>
)
}
}
Counter.propTypes={
caption:PropTypes.string.isRequired,
initValue:PropTypes.number,
onUpdate:PropTypes.func
};
Counter.defaultProps={
initValue:0,
onUpdate:f=>f
};