React 標榜自己是 MVC 里面 V 的部分,那么 Flux 就相當于添加 M 和 C 的部分。
Flux 是 Facebook 使用的一套前端應用的架構模式。
一個 Flux 應用主要包含四個部分:
the dispatcher
處理動作分發(fā),維護 Store 之間的依賴關系
the stores
數(shù)據(jù)和邏輯部分
the views
React 組件,這一層可以看作 controller-views,作為視圖同時響應用戶交互
the actions
提供給 dispatcher 傳遞數(shù)據(jù)給 store
針對上面提到的 Flux 這些概念,需要寫一個簡單的類庫來實現(xiàn)銜接這些功能,市面上有很多種實現(xiàn),這里討論 Facebook 官方的一個實現(xiàn) Dispatcher.js
先來了解一下 Flux 的核心“單向數(shù)據(jù)流“怎么運作的:
Action -> Dispatcher -> Store -> View
更多時候 View 會通過用戶交互觸發(fā) Action,所以一個簡單完整的數(shù)據(jù)流類似這樣:
http://wiki.jikexueyuan.com/project/react-tutorial/images/flux-overview.png" alt="flux overview" />
整個流程如下:
setState 更新組件 UI所有的狀態(tài)都由 Store 來維護,通過 Action 傳遞數(shù)據(jù),構成了如上所述的單向數(shù)據(jù)流循環(huán),所以應用中的各部分分工就相當明確,高度解耦了。
這種單向數(shù)據(jù)流使得整個系統(tǒng)都是透明可預測的。
一個應用只需要一個 dispatcher 作為分發(fā)中心,管理所有數(shù)據(jù)流向,分發(fā)動作給 Store,沒有太多其他的邏輯(一些 action creator 方法也可以放到這里)。
Dispatcher 分發(fā)動作給 Store 注冊的回調函數(shù),這和一般的訂閱/發(fā)布模式不同的地方在于:
基于 Flux 的架構思路,Dispatcher.js 提供的 API 很簡單:
waitFor() 使用dispatcher 只是一個粘合劑,剩余的 Store、View、Action 就需要按具體需求去實現(xiàn)了。
接下來結合 flux-todomvc 這個簡單的例子,提取其中的關鍵部分,看一下實際應用中如何銜接 Flux 整個流程,希望能對 Flux 各個部分有更直觀深入的理解。
首先要創(chuàng)建動作,通過定義一些 action creator 方法來創(chuàng)建,這些方法用來暴露給外部調用,通過 dispatch 分發(fā)對應的動作,所以 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ù)的動作
toggleCompleteAll: function() {
AppDispatcher.dispatch({
actionType: TodoConstants.TODO_TOGGLE_COMPLETE_ALL
});
}
};
AppDispatcher 直接繼承自
Dispatcher.js,在這個簡單的例子中沒有提供什么額外的功能。TodoConstants 定義了動作的類型名稱常量。
類似 create、updateText 就是 action creator,這兩個動作會通過 View 上的用戶交互觸發(fā)(比如輸入框)。 除了用戶交互會創(chuàng)建動作,服務端接口調用也可以用來創(chuàng)建動作,比如通過 Ajax 請求的一些初始數(shù)據(jù)也可以創(chuàng)建動作提供給 dispatcher,再分發(fā)給 store 使用這些初始數(shù)據(jù)。
action creators are nothing more than a call into the dispatcher.
可以看到所謂動作就是用來封裝傳遞數(shù)據(jù)的,動作只是一個簡單的對象,包含兩部分:payload(數(shù)據(jù))和 type(類型),type 是一個字符串常量,用來標識動作。
Stores 包含應用的狀態(tài)和邏輯,不同的 Store 管理應用中不同部分的狀態(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);
}
});
// 注冊到 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 的回調函數(shù)中會接受到分發(fā)的 action,因為每個 action 都會分發(fā)給所有注冊的回調,所以回調函數(shù)里面要判斷這個 action 的 type 并調用相關的內部方法處理更新 action 帶過來的數(shù)據(jù)(payload),再通知 view 數(shù)據(jù)變更。
Store 里面不會暴露直接操作數(shù)據(jù)的方法給外部,暴露給外部調用的方法都是 Getter 方法,沒有 Setter 方法,唯一更新數(shù)據(jù)的手段就是通過在 dispatcher 注冊的回調函數(shù)。
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());
}
});
一個 View 可能關聯(lián)多個 Store 來管理不同部分的狀態(tài),得益于 React 更新 View 如此簡單(setState),復雜的邏輯都被 Store 隔離了。