- Flux是Facebook官方提出的一套前端应用框架模式。它的核心概念就是单向数据流。
- 更像是一种软件开发模式,而不是具体的一个框架,所以基于Flux存在很多实现方式。其实,使用Flux框架开发程序不需要引入很多代码,关键是它内在的思想。
单向数据流是Flux的核心。Flux的单向数据流是怎么具体运作的呢?
Action就是用来描述一个行为的对象,里面有相关的信息,比如说一个创建文章的Action可以是:
{
actionName:'creat-post',
data:{
content:'new stuff'
}
}
Dispather是一个信息的分发中心,它是Action和Store的连接中心。Dispatch可以使用dispatch方法执行一个Action,并且可以用register方法注册并回调,在回调方法中处理Store中的内容。
Store处理完毕之后,它可以使用emit方法向其他发送名为‘change’的广播,告知他们Store已经发生变更。
View层监听change事件,一旦change事件被触发,那么该层可以调用setState来更新整个UI。
//dispatcher/AppDispatcher.js
//实例化一个Dispatcher并且返回
import {Dispatcher} from 'flux';
export default new Dispatcher();
当产生一个Action(新建或者删除)时,
//.components/Todo.jsx
import TodoAction from '../actions/TodoAction;
...
class Todo extends React.Component {
//数据
construstor(props){
super(props);
this.createTodo=this.createTodo.bind(this);
this.deleteTodo=this.deleteTodo.bind(this);
}
//方法:新建todo
createTodo(){
TodoAction.create({id:uuid.v4(),content:'3rd stuff'});
}
//方法:删除todo
deleteTodo(){
TodoAction.delete(id);
}
//模板
render(){
return(<div>
<List items={this.state.todos} onDelete={this.deleteTodo}>
<CreateButton onClick={this.createTodo}>
</div>)
}
}
export default Todo;
当点击按钮是,一个特殊的action会被触发,并且交给Dispatcher处理。
//./actions/TodoAction
import AppDispatcher from '../dispatcher/AppDispatcher';
const TodoAction = {
creat(todo){
AppDispatcher.dispatch({
actionType:'CREATE_TODO',
todo
});
},
delete(id){
AppDispatcher.dispatch({
actionType:'DELETE_TODO',
id
});
}
};
export defalut TodoAction;
只是一个普通的JavaScript Object,用一个actionType字段来表明这个action的用途,用另一个字段来表明它所传递的信息。
在这里,dispatch的是一个对象,但是当应用的复杂程度不断增加的时候,就可能在不同的view中dispatch相同的
对象,而且必须用着相同的actionType,还要记牢数据的格式,这都不利于代码复用。所以Flux提出了一个新的概念,称为action creator,其实就是将这些数据抽象到一个函数中。就像在ToDoAction里面写的一样。
//在TodoAction中定义的Action Creators
const TodoAction ={
//用一个函数包裹Dispatcher.dispatch方法 actionCreator
create(todo){
AppDispatcher.dispatch({
actionType:'CREATE_TODO',
todo
});
}
...
}
//那么现在调用action就可以像Todo.jsx中写的一样。
TodoAction.create({id:uuid.v4(),content:'3rd stuff'});
store:是整个程序所需要的数据。store是单例模式的,在整个程序中,每种store都仅有一个实例。例:现在来创建TodoStore,它存放了所有的文章列表。不同类型的数据应该创建多个store,假如程序里面存在用户信息,那么还可以新建一个UserStore.js。
//./store/TodoStore.js
//单件类型的一个JavaScript Object
const TodoStore={
//存放所有的文章列表,里面有两条默认的数据
todos:[
{id:uuid.v4(),
content:'first one'},
{id:uuid.v4(),
content:'2nd two'},
],
getAll(){return this.todos;},
addTodo(todo){
this.todos.push(todo);
},
deleteTodo(id){
this.todos=this.todos.filter(item=>item.id!==id);
}
}
Dispatcher的另外一个API方法是register,它可以注册不同事件的处理回调,并且在回调中对store进行处理。
//./store/TodoStore.js
...
AppDispatcher.register(action=>{
switch(action.actionType){
case 'CREATE_TODO':
TodoStore.addTodo(action.todo);
break;
case 'DELETE_TODO':
TodoStore.deleteTodo(action.id);
break;
default:
//默认操作
}
})
...
每个action对应dispatcher传过来的action,包含actionType和对应的数据,store是更新数据的唯一场所,这是Flux的重要理念。
action和Dispatcher并不和数据打交道,无法做数据操作。
Dispatcher的两个重要的方法:
- register方法:负责注册各种actionType的回调,并且在回调中操作store;
- dispatch方法:这个方法用来触发对应类型的回调。
Pub-Sub(订阅-发布)模式的代码如下:
var EventEmitter = require('events');
var myEmitter = new EventEmitter();
myEmitter.on('event',function(name){
console.log('my name is '+name);
})
myEmitter.emit('event','张三');
//打印:my name is 张三
Store发生了改变,有它来通知View,然后由View来展示新的数据了。
现在的代码无法完成这个功能,上面提到的Pub-Sub(订阅-发布)模式,如果给store添加上这个特性,就能很方便的把store和view联系在一起。在这里借助Node.js标准库EventEmitter在浏览器中实现。
npm install events -save
//使用Object.assign方法把EventEmitter.prototype挂载到TodoStore上
const TodoStore=Object.assign({},EventEmitter.prototype,{
...
emitChange(){
this.emit('change');
},
addChangeListener(callback){
this.on('change',callback);
},
removeChangeListener(callback){
this.removeListener('change',callback);
},
...
});
AppDispatcher.register((action)>{
switch(action.actionType){
case 'CREATE_TODO':
TodoStore.addTodo(action.todo);
//TodoStore已经更改,发送了一个广播
TodoStore.emitChange();
break;
case 'DELETE_TODO':
TodoStore.deleteTodo(action.id);
TodoStore.emitChange();
break;
default:
//默认操作
}
})
export defalut TodoAction;
store的变化已经使用emit方法广播出去,需要view层接受这个变化的信号,同时更新UI。
第一步:在组件刚初始化完毕的时候监听store的change事件,这样在store触发这个事件的时候,就会触发回调。
第二步:在生命周期函数componentDidMount中注册这个事件,在componentWillUnmount中清除这个事件绑定。
//Todo.jsx
class Todo extends React.Component{
construstor(props){
super(props);
this.state ={
todos:TodoStore.getAll()
};
this.createTodo=this.createTodo.bind(this);
this.deleteTodo=this.deleteTodo.bind(this);
this.onChange=this.onChange.bind(this);
}
//初始化时在store中注册这个事件
componentDidMount(){
TosoStore.addChangeListener(this.onChange);
}
//组件卸载时清除这个事件绑定
componentWillUnmount(){
TosoStore.removeChangeListener(this.onChange);
}
//store改变后触发的回调函数,用setState来更新整个UI
onChange(){
this.setState({
todos:TodoStore.getAll()
})
}
}
优点:
Flux能够合理将数据和组件的state分离,保持清晰的数据流,提 供了可预测的状态,避免的了多向数据流带来的混乱和维护困难的问题。适用于比较复杂的多人项目。
缺点:
实现Flux架构会增加代码量,应用简单时没有必要使用Flux。