美文网首页
Flux架构

Flux架构

作者: 珍珠林 | 来源:发表于2017-05-21 13:56 被阅读0次

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

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

    • Flux流程


      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的事件回调,响应这个事件并重新渲染界面。

    相关文章

      网友评论

          本文标题:Flux架构

          本文链接:https://www.haomeiwen.com/subject/rblsxxtx.html