在学习Redux之前,了解下Flux的思想是非常必要的。Flux是和React同时面世的,在2013年,Face-book公司让React亮相的同时,也推出了Flux框架,React和Flux相辅相成,Facebook认为两者结合在一起才能构建大型的JavaScript应用。做一个容易理解的对比,React是用来替换jQuery的,那么Flux就是以替换Backbone.js、Ember.js等MVC一族框架为目的。
Flux框架结构大致如下:
Flux框架一个Flux应用包含四个部分,我们先粗略了解一下:
1、Dispatcher,处理动作分发,维持Store之间的依赖关系;
2、Store,负责存储数据和处理数据相关逻辑;
3、Action,驱动Dispatcher的JavaScript对象;
4、View,视图部分,负责显示用户界面。
Flux应用
下面我们来做一个flux demo,实现加减和求和;
为了使用Flux,首先通过命令行在项目目录下安装Flux。
npm install –-save flux
1、Dispatcher
首先,我们要创造一个Dispatcher,几乎所有应用都只需要拥有一个Dispatcher,在src/AppDispatcher.js中,我们创造这个唯一的Dispatcher对象,代码如下:
import {Dispatcher} from 'flux';
export default new Dispatcher();
Dispatcher存在的作用,就是用来派发action,接下来我们就来定义应用中涉及的action。
2、Action
action对象必须有一个名为type的字段,代表这个action对象的类型,为了记录日志和debug方便,这个type应该是字符串类型。定义action通常需要两个文件,一个定义action的类型,一个定义action的构造函数(也称为action creator)。分成两个文件的主要原因是在Store中会根据action类型做不同操作,也就有单独导入action类型的需要。在src/ActionTypes.js中,我们定义action的类型,代码如下:
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
在这个例子中,用户只能做两个动作,一个是点击“+”按钮,一个是点击“-”按钮,所以我们只有两个action类型INCREMENT和DECREMENT。现在我们在src/Actions.js文件中定义action构造函数:
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
});
};
在Actions.js文件中,引入了ActionTypes和AppDispatcher,看得出来是要直接使用Dispatcher。这个Actions.js导出了两个action构造函数increment和decrement,当这两个函数被调用的时候,创造了对应的action对象,并立即通过AppDispatcher.dispatch函数派发出去。
3.Store
一个Store也是一个对象,这个对象存储应用状态,同时还要接受Dispatcher派发的动作,根据动作来决定是否要更新应用状态。现在,我们创造两个Store,一个是负责加减的CounterStore,另一个就是为总数服务的SummaryStore。
我们首先添加CounterStore,放在src/stores/CounterStore.js文件中。
先看定义CounterStore的代码,如下所示:
import AppDispatcher from '../AppDispatcher.js';
import * as ActionTypes from '../ActionTypes.js';
import {EventEmitter} from 'events';
const CHANGE_EVENT = 'changed';
const counterValues = {
'First': 10,
'Second': 20,
'Third': 30
};
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);
}
});
export default CounterStore;
上面实现的Store只有注册到Dispatcher实例上才能真正发挥作用,所以还需要增加下列代码:
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();
}
});
接下来,我们再来看看另一个Store,也就是代表所有计数器计数值总和的Store,在src/stores/SummaryStore.js中有源代码。
import AppDispatcher from '../AppDispatcher.js';
import * as ActionTypes from '../ActionTypes.js';
import CounterStore from './CounterStore.js';
import {EventEmitter} from 'events';
const CHANGE_EVENT = 'changed';
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() {
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)) {
AppDispatcher.waitFor([CounterStore.dispatchToken]);
SummaryStore.emitChange();
}
});
export default SummaryStore;
4、View
存在于Flux框架中的React组件需要实现以下几个功能:
1.创建时要读取Store上状态来初始化组件内部状态;
2.当Store上状态发生变化时,组件要立刻同步更新内部状态保持一致;
3.View如果要改变Store状态时,必须而且只能派发action;
View层我们拆分三个组件,ControlPanel、Control、Summary;
先看src/views/ControlPanel.js,代码如下:
import React, { Component } from 'react';
import Counter from './Counter.js';
import Summary from './Summary.js';
class ControlPanel extends Component {
render() {
return (
<div>
<Counter caption="First" />
<Counter caption="Second" />
<Counter caption="Third" />
<hr/>
<Summary />
</div>
);
}
}
export default ControlPanel;
看下两个子组件,首先是Counter:
import React, { Component, PropTypes } from 'react';
import * as Actions from '../Actions.js';
import CounterStore from '../stores/CounterStore.js';
class Counter extends Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
this.state = {
count: CounterStore.getCounterValues()[props.caption]
}
}
shouldComponentUpdate(nextProps, nextState) {
return (nextProps.caption !== this.props.caption) ||
(nextState.count !== this.state.count);
}
componentDidMount() {
CounterStore.addChangeListener(this.onChange);
}
componentWillUnmount() {
CounterStore.removeChangeListener(this.onChange);
}
onChange() {
const newCount = CounterStore.getCounterValues()[this.props.caption];
this.setState({count: newCount});
}
onClickIncrementButton() {
Actions.increment(this.props.caption);
}
onClickDecrementButton() {
Actions.decrement(this.props.caption);
}
render() {
const {caption} = this.props;
return (
<div>
<button onClick={this.onClickIncrementButton}>+</button>
<button onClick={this.onClickDecrementButton}>-</button>
<span>{caption} count: {this.state.count}</span>
</div>
);
}
}
Counter.propTypes = {
caption: PropTypes.string.isRequired
};
export default Counter;
其次是求和的Summary
import React, { Component } from 'react';
import SummaryStore from '../stores/SummaryStore.js';
class Summary extends Component {
constructor(props) {
super(props);
this.onUpdate = this.onUpdate.bind(this);
this.state = {
sum: SummaryStore.getSummary()
}
}
componentDidMount() {
SummaryStore.addChangeListener(this.onUpdate);
}
componentWillUnmount() {
SummaryStore.removeChangeListener(this.onUpdate);
}
onUpdate() {
this.setState({
sum: SummaryStore.getSummary()
})
}
render() {
return (
<div>Total Count: {this.state.sum}</div>
);
}
}
export default Summary;
这个简单的加减求和demo就做好了,效果如下:
效果图
网友评论