鍍金池/ 教程/ HTML/ Flux
React 組件
Redux 的基礎(chǔ)概念
JSX
DOM 操作
在 React 應(yīng)用中使用 Redux
進(jìn)化 Flux
Webpack 配置 React 開發(fā)環(huán)境
服務(wù)器端渲染
組合組件
表單
屬性擴(kuò)散
開發(fā)環(huán)境配置
組件生命周期
Data Flow
JSX 與 HTML 的差異
組件間通信
使用 JSX
事件處理
Flux
React 概覽
Mixins
Redux

Flux

React 標(biāo)榜自己是 MVC 里面 V 的部分,那么 Flux 就相當(dāng)于添加 M 和 C 的部分。

Flux 是 Facebook 使用的一套前端應(yīng)用的架構(gòu)模式。

一個(gè) Flux 應(yīng)用主要包含四個(gè)部分:

  • the dispatcher

    處理動(dòng)作分發(fā),維護(hù) Store 之間的依賴關(guān)系

  • the stores

    數(shù)據(jù)和邏輯部分

  • the views

    React 組件,這一層可以看作 controller-views,作為視圖同時(shí)響應(yīng)用戶交互

  • the actions

    提供給 dispatcher 傳遞數(shù)據(jù)給 store

針對(duì)上面提到的 Flux 這些概念,需要寫一個(gè)簡(jiǎn)單的類庫來實(shí)現(xiàn)銜接這些功能,市面上有很多種實(shí)現(xiàn),這里討論 Facebook 官方的一個(gè)實(shí)現(xiàn) Dispatcher.js

單向數(shù)據(jù)流

先來了解一下 Flux 的核心“單向數(shù)據(jù)流“怎么運(yùn)作的:

Action -> Dispatcher -> Store -> View

更多時(shí)候 View 會(huì)通過用戶交互觸發(fā) Action,所以一個(gè)簡(jiǎn)單完整的數(shù)據(jù)流類似這樣:

http://wiki.jikexueyuan.com/project/react-tutorial/images/flux-overview.png" alt="flux overview" />

整個(gè)流程如下:

  • 首先要有 action,通過定義一些 action creator 方法根據(jù)需要?jiǎng)?chuàng)建 Action 提供給 dispatcher
  • View 層通過用戶交互(比如 onClick)會(huì)觸發(fā) Action
  • Dispatcher 會(huì)分發(fā)觸發(fā)的 Action 給所有注冊(cè)的 Store 的回調(diào)函數(shù)
  • Store 回調(diào)函數(shù)根據(jù)接收的 Action 更新自身數(shù)據(jù)之后會(huì)觸發(fā)一個(gè) change 事件通知 View 數(shù)據(jù)更改了
  • View 會(huì)監(jiān)聽這個(gè) change 事件,拿到對(duì)應(yīng)的新數(shù)據(jù)并調(diào)用 setState 更新組件 UI

所有的狀態(tài)都由 Store 來維護(hù),通過 Action 傳遞數(shù)據(jù),構(gòu)成了如上所述的單向數(shù)據(jù)流循環(huán),所以應(yīng)用中的各部分分工就相當(dāng)明確,高度解耦了。

這種單向數(shù)據(jù)流使得整個(gè)系統(tǒng)都是透明可預(yù)測(cè)的。

Dispatcher

一個(gè)應(yīng)用只需要一個(gè) dispatcher 作為分發(fā)中心,管理所有數(shù)據(jù)流向,分發(fā)動(dòng)作給 Store,沒有太多其他的邏輯(一些 action creator 方法也可以放到這里)。

Dispatcher 分發(fā)動(dòng)作給 Store 注冊(cè)的回調(diào)函數(shù),這和一般的訂閱/發(fā)布模式不同的地方在于:

  • 回調(diào)函數(shù)不是訂閱到某一個(gè)特定的事件/頻道,每個(gè)動(dòng)作會(huì)分發(fā)給所有注冊(cè)的回調(diào)函數(shù)
  • 回調(diào)函數(shù)可以指定在其他回調(diào)之后調(diào)用

基于 Flux 的架構(gòu)思路,Dispatcher.js 提供的 API 很簡(jiǎn)單:

  • register(function callback): string 注冊(cè)回調(diào)函數(shù),返回一個(gè) token 供在 waitFor() 使用
  • unregister(string id): void 通過 token 移除回調(diào)
  • waitFor(array ids): void 在指定的回調(diào)函數(shù)執(zhí)行之后才執(zhí)行當(dāng)前回調(diào)。這個(gè)方法只能在分發(fā)動(dòng)作的回調(diào)函數(shù)中使用
  • dispatch(object payload): void 分發(fā)動(dòng)作 payload 給所有注冊(cè)回調(diào)
  • isDispatching(): boolean 返回 Dispatcher 當(dāng)前是否處在分發(fā)的狀態(tài)

dispatcher 只是一個(gè)粘合劑,剩余的 Store、View、Action 就需要按具體需求去實(shí)現(xiàn)了。

接下來結(jié)合 flux-todomvc 這個(gè)簡(jiǎn)單的例子,提取其中的關(guān)鍵部分,看一下實(shí)際應(yīng)用中如何銜接 Flux 整個(gè)流程,希望能對(duì) Flux 各個(gè)部分有更直觀深入的理解。

Action

首先要?jiǎng)?chuàng)建動(dòng)作,通過定義一些 action creator 方法來創(chuàng)建,這些方法用來暴露給外部調(diào)用,通過 dispatch 分發(fā)對(duì)應(yīng)的動(dòng)作,所以 action creator 也稱作 dispatcher helper methods 輔助 dipatcher 分發(fā)。 參見 actions/TodoActions.js

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 數(shù)據(jù)的動(dòng)作
  toggleCompleteAll: function() {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
    });
  }
};

AppDispatcher 直接繼承自 Dispatcher.js,在這個(gè)簡(jiǎn)單的例子中沒有提供什么額外的功能。TodoConstants 定義了動(dòng)作的類型名稱常量。

類似 createupdateText 就是 action creator,這兩個(gè)動(dòng)作會(huì)通過 View 上的用戶交互觸發(fā)(比如輸入框)。 除了用戶交互會(huì)創(chuàng)建動(dòng)作,服務(wù)端接口調(diào)用也可以用來創(chuàng)建動(dòng)作,比如通過 Ajax 請(qǐng)求的一些初始數(shù)據(jù)也可以創(chuàng)建動(dòng)作提供給 dispatcher,再分發(fā)給 store 使用這些初始數(shù)據(jù)。

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

可以看到所謂動(dòng)作就是用來封裝傳遞數(shù)據(jù)的,動(dòng)作只是一個(gè)簡(jiǎn)單的對(duì)象,包含兩部分:payload(數(shù)據(jù))和 type(類型),type 是一個(gè)字符串常量,用來標(biāo)識(shí)動(dòng)作。

Store

Stores 包含應(yīng)用的狀態(tài)和邏輯,不同的 Store 管理應(yīng)用中不同部分的狀態(tài)。如 stores/TodoStore.js

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 = {};

// 先定義一些數(shù)據(jù)處理方法
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 數(shù)據(jù)
  getAll: function() {
    return _todos;
  },
  // 觸發(fā) change 事件
  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },
  // 提供給外部 View 綁定 change 事件
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  }
});

// 注冊(cè)到 dispatcher,通過動(dòng)作類型過濾處理當(dāng)前 Store 關(guān)心的動(dòng)作
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 注冊(cè)給 dispatcher 的回調(diào)函數(shù)中會(huì)接受到分發(fā)的 action,因?yàn)槊總€(gè) action 都會(huì)分發(fā)給所有注冊(cè)的回調(diào),所以回調(diào)函數(shù)里面要判斷這個(gè) action 的 type 并調(diào)用相關(guān)的內(nèi)部方法處理更新 action 帶過來的數(shù)據(jù)(payload),再通知 view 數(shù)據(jù)變更。

Store 里面不會(huì)暴露直接操作數(shù)據(jù)的方法給外部,暴露給外部調(diào)用的方法都是 Getter 方法,沒有 Setter 方法,唯一更新數(shù)據(jù)的手段就是通過在 dispatcher 注冊(cè)的回調(diào)函數(shù)。

View

View 就是 React 組件,從 Store 獲取狀態(tài)(數(shù)據(jù)),綁定 change 事件處理。如 components/TodoApp.react.js

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.addChangeListener(this._onChange);
  },

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

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

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

一個(gè) View 可能關(guān)聯(lián)多個(gè) Store 來管理不同部分的狀態(tài),得益于 React 更新 View 如此簡(jiǎn)單(setState),復(fù)雜的邏輯都被 Store 隔離了。

更多資料