您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

React Flux

React Flux

React 标榜自己是 MVC 里面 V 的部分,那么 Flux 就相当于 M 和 C 的部分。

Flux 是 Facebook 使用的一套前端应用的架构模式。

Flux 应用主要包含四个部分:

the dispatcher

处理动作分发,维护 Store 之依赖关系

the stores

数据和逻辑部分

the views

React 组件,这一层可以看作 controller-views,作为视图同时响应交互

the actions

提供给 dispatcher 传递数据给 store

针对上面提到的 Flux 这些概念,需要写简单的类库来实现衔接这些,市面上有很多种实现,这里讨论 Facebook 官方的实现 

单向数据流

先来了解一下 Flux 的核心“单向数据流“怎么运作的:

Action -> Dispatcher -> Store -> View

更多时候 View 会通过交互触发 Action,所以简单完整的数据流类似这样:

整个流程如下:

首先要有 action,通过定义一些 action creator 根据需要创建 Action 提供给 dispatcher

View 层通过交互(比如 onClick)会触发 Action

Dispatcher 会分发触发的 Action 给所有的 Store 的回调

Store 回调根据接收的 Action 更新自身数据之后会触发 change 事件 View 数据更改了

View 会监听这个 change 事件,拿到对应的新数据并 setState 更新组件 UI

所有的状态都由 Store 来维护,通过 Action 传递数据,构成了如上所述的单向数据流循环,所以应用中的各部分分工就相当明确,高度解耦了。

这种单向数据流使得整个系统都是透明可预测的。

Dispatcher

应用只需要 dispatcher 作为分发中心,管理所有数据流向,分发动作给 Store,没有太多其他的逻辑(一些 action creator 也可以放到这里)。

Dispatcher 分发动作给 Store 的回调,这和一般的/发布模式不同的地方在于:

回调不是到某特定的事件/频道,每个动作会分发给所有的回调

回调可以指定在其他回调之后

基于 Flux 的架构思路, 提供的 API 很简单:

register(function callback): string 回调,返回 token 供在 waitFor() 使用

unregister(string id): void 通过 token 移除回调

waitFor(array  ids): void  在指定的回调执行之后才执行当前回调。这个只能在分发动作的回调中使用

dispatch(object payload): void 分发动作 payload 给所有回调

isDispatching(): boolean 返回 Dispatcher 当前是否处在分发的状态

dispatcher 只是粘合剂,剩余的 Store、View、Action 就需要按具体需求去实现了。

接下来结合  这个简单的例子,其中的关键部分,看一下实际应用中如何衔接 Flux 整个流程,希望能对 Flux 各个部分有更直观深入的理解。

Action

首先要创建动作,通过定义一些 action creator 来创建,这些用来暴露给外部,通过 dispatch 分发对应的动作,所以 action creator 也称作 dispatcher helper methods 辅助 dipatcher 分发。 参见 

var AppDispatcher = require('../dispatcher/AppDispatcher');
var TodoConstants = require('../constants/TodoConstants');

var TodoActions = {
  create: function(text) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_CREATE,
      text: text
    });
  },

  updateText: function(id, text) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_UPDATE_TEXT,
      id: id,
      text: text
    });
  },

  // 不带 payload 数据的动作
  toggleCompleteAll: function() {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
    });
  }
};

AppDispatcher 直接继承自 Dispatcher.js,个简单的例子中没有提供什么额外的。TodoConstants 定义了动作的类型常量。

类似 create、updateText 就是 action creator,这两个动作会通过 View 上的交互触发(比如输入框)。 除了交互会创建动作,服务端接口也可以用来创建动作,比如通过 Ajax 请求的一些初始数据也可以创建动作提供给 dispatcher,再分发给 store 使用这些初始数据。

action creators are nothing more than a call into the dispatcher.

可以看到所谓动作就是用来封装传递数据的,动作只是简单的对象,包含两部分:payload(数据)和 type(类型),type 是字符串常量,用来标识动作。

Store

Stores 包含应用的状态和逻辑,不同的 Store 管理应用中不同部分的状态。如 

var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TodoConstants = require('../constants/TodoConstants');
var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var _todos = {};

// 先定义一些数据处理
function create(text) {
  var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
  _todos[id] = {
    id: id,
    complete: false,
    text: text
  };
}
function update(id, updates) {
  _todos[id] = assign({}, _todos[id], updates);
}
// ...

var TodoStore = assign({}, EventEmitter.prototype, {
  // Getter 暴露给外部 Store 数据
  getAll: function() {
    return _todos;
  },
  // 触发 change 事件
  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },
  // 提供给外部 View 绑定 change 事件
  addener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  }
});

// 到 dispatcher,通过动作类型过滤处理当前 Store 关心的动作
AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case TodoConstants.TODO_CREATE:
      text = action.text.trim();
      if (text !== '') {
        create(text);
      }
      TodoStore.emitChange();
      break;

    case TodoConstants.TODO_UPDATE_TEXT:
      text = action.text.trim();
      if (text !== '') {
        update(action.id, {text: text});
      }
      TodoStore.emitChange();
      break;
  }
});

在 Store 给 dispatcher 的回调中会接受到分发的 action,因为每个 action 都会分发给所有的回调,所以回调里面要判断这个 action 的 type 并相关的内部处理更新 action 带过来的数据(payload),再 view 数据变更。

Store 里面不会暴露直接操作数据的给外部,暴露给外部的都是 Getter ,没有 Setter ,唯一更新数据的手段就是通过在 dispatcher 的回调。

View

View 就是 React 组件,从 Store 状态(数据),绑定 change 事件处理。如

var React = require('react');
var TodoStore = require('../stores/TodoStore');

function getTodoState() {
  return {
    allTodos: TodoStore.getAll(),
    areAllComplete: TodoStore.areAllComplete()
  };
}

var TodoApp = React.createClass({

  getInitialState: function() {
    return getTodoState();
  },

  componentDidMount: function() {
    TodoStore.addener(this._onChange);
  },

  componentWillUnmount: function() {
    TodoStore.removeener(this._onChange);
  },

  render: function() {
    return <div>/*...*/</div>
  },

  _onChange: function() {
    this.setState(getTodoState());
  }
});

View 可能关联多个 Store 来管理不同部分的状态,得益于 React 更新 View 如此简单(setState),复杂的逻辑都被 Store 隔离了。

参考资料

 很简洁明了的 Slide

 更复杂一点的例子

我们可以先通过对比 R 和 Flux 的实现来感受一下 R 带来的惊艳。

首先是 action creators,Flux 是直接在 action 里面 dispatch:

export function addTodo(text) {
  AppDispatcher.dispatch({
    type: ActionTypes.ADD_TODO,
    text: text
  });
}

R 把它简化成了这样:

export function addTodo(text) {
  return {
    type: ActionTypes.ADD_TODO,
    text: text
  };
}

这一步把 dispatcher 和 action 解藕了,很快我们就能看到它带来的好处。

接下来是 Store,这是 Flux 里面的 Store:

let _todos = [];
const TodoStore = Object.assign(new EventEmitter(), {
  getTodos() {
    return _todos;
  }
});
AppDispatcher.register(function (action) {
  switch (action.type) {
  case ActionTypes.ADD_TODO:
    _todos = _todos.concat([action.text]);
    TodoStore.emitChange();
    break;
  }
});
export default TodoStore;

R 把它简化成了这样:

const initialState = { todos: [] };
export default function TodoStore(state = initialState, action) {
  switch (action.type) {
  case ActionTypes.ADD_TODO:
    return { todos: state.todos.concat([action.text]) };
  default:
    return state;
}

同样把 dispatch 从 Store 里面剥离了,Store 变成了 pure function:(state, action) => state

什么是 pure function

如果没有任何副作用(side-effects),不会影响任何外部状态,对于任何相同的输入(参数),无论何时这个总是返回同样的结果,这个就是 pure function。所谓 side-effects 就是会改变外部状态的因素 ,比如 Ajax 请求就有 side-effects,因为它带来了不确定性。

所以现在 Store 不再拥有状态,而只是管理状态,所以首先要明确概念,Store 和 State 是有区别的,Store 并不是简单的数据结构,State 才是,Store 会包含一些来管理 State,比如/ State。

基于这样的 Store,可以做很多扩展,这也是 R 强大之处。

来源:


联系我
置顶