概述
Flux 是用来构建用户端 Web 应用的架构,大体分为四个部分:Views, Stores 和 Dispatcher,Actions。
Views: 负责渲染应用中所有与用户直接交互的界面,并从Store获取数据。
Stores: 负责管理应用的数据。 可以按照业务逻辑划分多个Store, 例如UserStore用来管理用户信息。
Dispatcher: 负责分发从Action过来的事件,传递给store,整个应用的调度中心,只有一个。
-
Actions: Actions是传递给Dispatcher的动作,包好Action物件和Action Creators.
- Action物件是指Action里包含的信息,主要包括ActionType (在Dispatcher中用来判断如何分发),以及其他该动作所携带的主要信息
- Action Creators 负责创建Actions,并传递给Dispatcher
flux应用的数据流向是一个单向数据流,由Action creators创建的Actions统一被传递到Dispacther,Dispacther在自己被注册的方法中按照Action物件来下发事件到对应的Store, Store处理完逻辑 发送一个变化的通知,然后所有监听了这个Store变化的页面更新响应。
创建项目
npm install -g create-react-app
create-react-app my-app
cd my-app/
npm start
启动~~
最终项目放在github上面 下载后
$ npm install
$ npm start
项目结构如下图
Actions
篇幅有限,贴出部分Action代码。
import Constants from '../Constants/Constants'
import TodoDispatcher from '../Dispatcher/TodoDispatcher'
let TodoAction = {
toggleItem(id) {
TodoDispatcher.dispatch({
id,
type: Constants.TOGGLEITEM
})
},
deleteItem(id) {
TodoDispatcher.dispatch({
id,
type: Constants.DELETEITEM
})
},
loadData() {
fetch('todos.json')
.then((data)=> data.json())
.then((todos)=>{
TodoDispatcher.dispatch({
todos,
type: Constants.LOADDATA
})
})
}
}
module.exports = TodoAction;
- Actions包含了所有的动作,所有的动作被传递给Dispatcher来做分发。
- Action物件,字典
{ id, type: Constants.TOGGLEITEM }
就是一个Action物件,包含了Action的所有信息,其中type就是dispatch分发时该Action的标识符。 - Action Creators,
toggleItem
就是一个Action Creators,用来创建Action,在我们的例子当中不能体现它的价值,复杂一点的项目对传递进来的参数可以再这个地方进行处理,这是一个很常见的场景。
Dispatcher
Flux 库的 Dispatcher 提供了一个 dispatch 函数,将接收到的 Actions 传递给所有注册的回调函数,回调函数由 Stores 提供。 flux源码
import { Dispatcher } from 'flux'
let TodoDispatcher = new Dispatcher()
module.exports = TodoDispatcher;
Store
import Constants from '../Constants/Constants'
import TodoDispatcher from '../Dispatcher/TodoDispatcher'
const CHANGE_TODOS = 'CHANGE_TODOS'
import EventEmitter from 'events'
let _emitter = new EventEmitter()
let todos = [];
let toggleItemList = (todos, id) => {
let newTodos = [...todos];
let target = newTodos.find((todo)=>{
return todo.id === id
})
if (target) {
target.checked = !target.checked
}
return newTodos
}
let deleteItemList = (todos, id) => {
let newTodos = [...todos];
//找到对应的索引
let idx = newTodos.findIndex((todo)=>{
return todo.id === id
})
//按照索引删除
newTodos.splice(idx,1)
return newTodos
}
let todoStore = {
getTodos() {
return todos;
},
addObserver(callback) {
_emitter.on(CHANGE_TODOS, callback)
return ()=> _emitter.removeListener(CHANGE_TODOS, callback)
},
dispatchToken: TodoDispatcher.register((action)=>{
console.log(action)
switch(action.type) {
case Constants.TOGGLEITEM:
todos = toggleItemList(todos, action.id);
break;
case Constants.DELETEITEM:
todos = deleteItemList(todos, action.id);
break;
case Constants.CREATEITEM:
todos = createItem(todos, action.title);
break;
case Constants.EDITITEM:
todos = editItem(todos, action.id, action.title);
break;
case Constants.LOADDATA:
todos = action.todos;
break;
default:
break;
}
_emitter.emit(CHANGE_TODOS)
})
}
module.exports = TodoDispatcher;
Store部分代码由以下几部分组成
- 业务逻辑
toggleItemList 、deleteItemList
都是具体的业务逻辑处理。 - 创建Store,
TodoDispatcher.register
注册所有需要分发的方法,在事件中按照被传递过来的action.type区分具体的业务实现,该方法返回值是一个字符串为该Store的唯一标示,当一个Action触发多个store的执行,并且有先后顺序,可以用这个标识排先后顺序,见waitFor方法。 - 实现 添加观察者的方法
addObserver
,在每一个业务逻辑处理完成之后,通过_emitter.emit(CHANGE_TODOS)
来发送事件,刷新页面。
Views
举一个TodoListContainer组件代码为例
import React, { Component } from 'react'
import TodoList from './TodoList'
import TodoStore from '../Store/TodoStore'
import TodoAction from '../Action/TodoAction'
class TodoListContainer extends Component {
constructor(props) {
super(props);
this.state = {
todos: TodoStore. getTodos(),
};
}
componentDidMount() {
this.addObserver = TodoStore. addObserver(()=>{
this.setState({
todos: TodoStore.getTodos()
})
})
}
componentWillUnmount() {
this.addObserver()
}
render () {
return (
<TodoList
todos={this.state.todos}
toggleItem={TodoAction.toggleItem}
deleteItem={TodoAction.deleteItem}
editItem={TodoAction.editItem}
/>
)
}
}
module.exports = TodoListContainer;
这个组件中
- 从TodoStore处获取数据,在状态机中接收,渲染界面。
- 在生命周期
componentDidMount
中监听TodoStore,一旦Store当中数据发生变化,就会通过发送这个事件来更新View,addListner
是一个添加监听者的方法,返回值是移除监听者的一个方法, 所以在该页面卸载时componentWillUnmount
通过调用返回的方法就可以移除监听者。 - 所有组件当中用到的事件,都来自TodoAction,用户的交互信息皆由Action采集传递给Dispatcher。
优化
flux提供了Flux Utils来优化现有的flux实现。
ReduceStore
import {ReduceStore} from 'flux/utils';
class TodoStore extends ReduceStore {
getInitialState() {
return [];
}
reduce(todos, action) {
switch (action.type) {
case Constants.TOGGLEITEM:
return toggleItemList(todos, action.id);
case Constants.DELETEITEM:
return deleteItemList(todos, action.id);
case Constants.CREATEITEM:
return createItem(todos, action.title);
case Constants.EDITITEM:
return editItem(todos, action.id, action.title);
case Constants.LOADDATA:
return action.todos;
default:
return todos;
}
}
}
//关联dispatcher
module.exports = new TodoStore(TodoDispatcher);
以上代码可以完全代替todoStore那整个字典,ReduceStore帮我们
- 处理了
addObserver
和发送页面刷新这样的通知。 - 去掉了todos这样的数据集合,我们只需要在外面使用
TodoStore.getState()
就可以获取到todos这样的数据集合。所以相对应的我们需要在页面接收数据时做一些改变。 - 导出的时候注意绑定。
constructor(props) {
super(props);
this.state = {
todos: TodoStore.getState(),
};
}
componentDidMount() {
this.addObserver = TodoStore.addListener(()=>{
this.setState({
todos: TodoStore.getState()
})
})
}
componentWillUnmount() {
this.addObserver()
}
其中我们将自定义的的getTodos
和addObserver
替换成ReduceStore
的方法。
Container
import {Container} from 'flux/utils';
static getStores() {
return [TodoStore];
}
static calculateState(prevState) {
return {
todos: TodoStore.getState(),
};
}
module.exports = Container.create(TodoListContainer);
用这些代码可以完全替换View里面的constructor
、 componentDidMount
、componentWillUnmount
里面的三个方法。Container实际上帮我们处理了这些事情
- 去除添加移除监听者的这些操作,全部转移到了
calculateState
方法中。 - 去除状态机里面的todos,在
calculateState
方法中字典的key就是我们状态机里面的值,依旧可以用this.state,todos来调用
- 注意导出的时候
Container.create(TodoListContainer)
参考链接
http://facebook.github.io/flux/docs/flux-utils.html#content
http://kongyixueyuan.com/