Flux架构

当应用复杂程度增加时,state会变得越来越复杂,只用React开发将会变得力不从心。不仅仅是view层的内容,还有其他的比如说数据流向、state管理、路由解决方案等。React的开发者推出了Flux机构及其官方实现。同时业内也推出了很多其他的Flux实现,其中又以Redux这个库为翘楚。
  Flux是Facebook官方提出的一套前端应用架构模式。核心是单向数据流。它更像一种软件开发模式,而不是具体的一个框架,所以基于Flux存在很多的实现。

  • 单向数据流
    单向数据流是Flux的核心。读者可能接触过MVC这种软件架构,它的数据流动是双向的。controller是model和view之间交互的媒介,它要处理view的交互操作,通知model进行更新,同时在操作成功后通知view更新。这种双向的模式在model和view的对应关系变得越来越复杂的时候,就会遇到很多困难,难以维护和调试。

  • Flux流程


    Flux架构_第1张图片
    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 (
      
); } } 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的事件回调,响应这个事件并重新渲染界面。

你可能感兴趣的:(Flux架构)