当应用复杂程度增加时,state会变得越来越复杂,只用React开发将会变得力不从心。不仅仅是view层的内容,还有其他的比如说数据流向、state管理、路由解决方案等。React的开发者推出了Flux机构及其官方实现。同时业内也推出了很多其他的Flux实现,其中又以Redux这个库为翘楚。
Flux是Facebook官方提出的一套前端应用架构模式。核心是单向数据流。它更像一种软件开发模式,而不是具体的一个框架,所以基于Flux存在很多的实现。
单向数据流
单向数据流是Flux的核心。读者可能接触过MVC这种软件架构,它的数据流动是双向的。controller是model和view之间交互的媒介,它要处理view的交互操作,通知model进行更新,同时在操作成功后通知view更新。这种双向的模式在model和view的对应关系变得越来越复杂的时候,就会遇到很多困难,难以维护和调试。-
Flux流程
Action:是用来描述单个行为的对象,比如说创建文章的Action可以是{actionName: 'createPost', data: {'content': 'new stuff'}}。
Dispatcher:Dispatcher是信息分发中心,是Action和Store的连接中心,Dispatcher可以使用dispatch方法执行一个Action,并且可以用register方法注册回调,在回调方法中处理Store的内容。
Store:Store处理完毕后,它可以使用emit方法向其他地方发送命名为change的广播,告知他妈Store已经发生变更。
View:View层监着change事件,一旦change事件被触发,那么该层可以调用setState来更新整个UI。
Flux架构示例
现在用Flux架构完成一个Todo代办事项的小程序。
- 项目结构
components/
--Todo.jsx(程序框架)
--List.jsx(代办事项列表)
--CreateButton.jsx(新建代办事项按钮)
actions/
--TodoAction.js(程序中所有的action)
dispatcher/
--AppDispatcher.js(程序中的总调度)
stores
--TodoStore.js(管理程序中的数据的存放)
- Dispatcher和action
可以把Dispatcher看作是一个调度中心,把action看作是应用程序的各种交互动作,而每个动作产生后都会交给Dispatcher这个调度中心来处理。Dispatcher有Facebook的官方实现,称为Flux Dispathcer:
// dispatcher/AppDispatcher.js
// 实例化一个Dispatcher并返回
import { Dispatcher } from 'flux';
export default new Dispatcher();
新建或删除一个Todo都会产生一个action:
// Todo.jsx
...
import TodoAction from '.../actions/TodoAction';
...
class Todo extends React.Component {
constructor(props) {
this.createTodo = this.createTodo.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
}
createTodo() {
// 创建Todo的事件回调
TodoAction.create({ id: uuid.v4(), content: '3rd stuff' });
}
deleteTodo(id) {
// 删除Todo的事件回调
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 = {
create(todo) {
AppDispatcher.dispatch({
actionType: 'CREATE_TODO',
todo
});
},
delete(id) {
AppDispatcher.dispatch({
actionType: 'DELETE_TODO',
id
});
}
};
export default TodoAction;
action只不过是一个普通的JavaScript Object,用一个actionType字段表明用途,另外一个字段表明它传递的信息。
在这里,dispatch的是一个对象,但是当应用复杂程度不断增加的时候,就可能在不同的view中dispatch相同的对象,而且必须有着相同的actionType,还要记牢数据的格式,这都不利于代码复用,所以Flux提出了一个新的概念,称为action creator,其实就是把这些数据抽象到一些函数中。就像在TodoAction里面写的一样:
// 在TodoAction中定义的Action Creators
const TodoAction = {
// 用一个函数包裹AppDispatcher.dispatch方法
actionCreator
create(todo) {
AppDispatcher.dispatch({
actionType: 'CREATE_TODO',
todo
});
},
...
};
- store和Dispatcher
store就是整个程序所需要的数据。store是单例的。现在来创建TodoStore,它存放了所有的文章列表。不同类型的数据应该创建多个store,假如程序里还存在用户信息,就应该再创建UserStore.js
// ./stores/TodoStore.js
// 单件类型的一个JavaScript Object
const TodoStore = {
// 存放所有文章的列表,里面有两条默认的数据
todos: [{ id: uuid.v4(), content: 'first one'}, { id: uuid.v4(), content: '2nd one '}],
getAll() {
return this.todos;
},
addTodo(todo) {
this.props.push(todo);
},
deleteTodo(id) {
this.todos = this.todos.filter(item => item.id !== id);
}
};
Dispatcher的另外一个API方法就是register,它可以注册不同事件的处理回调,并且在回调中队store进行处理。
// ./stores/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对应dispatch传过来的一个action,包含actionType和对应的数据。store是更新数据的唯一场所,这是Flux的重要概念。actoin和Dispatcher并不和数据打交道。
- store和view
现在,store已经发生变化,是时候由它来通知view,让view展示新的数据了。我们借助Node.js标准库EventEmitter,让store加上事件订阅特性,就可以把store和view联系在一起了:
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 default TodoStore;
store的变化已经使用emit方法广播出去,那么view层现在要做的就是接收这个变化的信号,同时更新UI。首先要在组件刚初始化的时候监听store的change事件,这样在store触发这个事件的时候,就会触发回调。那么,我们回到Todo.jsx组件中,在它的生命周期函数中加上这些事件监听:
// Todo.jsx
class Todo extends React.Component {
constructor(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);
}
componentDidMount() {
// 初始化的时候在store中注册这个事件
TodoStore.addChangeListener(this.onChange);
}
componentWillUnmount() {
// 组件卸载的时候记得要清除这个事件绑定
TodoStore.removeChangeListener(this.onChange);
}
onChange() {
// store改变后触发的回到,用setState来更新整个UI
this.setState({
todos: TodoStore.getAll()
});
}
...
}
到现在已经完成了Flux的整个流程。当用户在view上有一个交互时,Dispatcher广播(dispatch方法)一个action(就是一个Object对象,里面包含action的类型和要传递的数据),在整个程序的总调度台(Dispatcher)里注册了各种类型的action,在对应的类型中,store(也是一个Object对象,实现了订阅-发布的功能)对这个action进行响应,对数据做响应的处理,然后触发一个自定义事件,同时在view上注册这个store的事件回调,响应这个事件并重新渲染界面。