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
先來了解一下 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è)流程如下:
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è)的。
一個(gè)應(yīng)用只需要一個(gè) dispatcher 作為分發(fā)中心,管理所有數(shù)據(jù)流向,分發(fā)動(dòng)作給 Store,沒有太多其他的邏輯(一些 action creator 方法也可以放到這里)。
Dispatcher 分發(fā)動(dòng)作給 Store 注冊(cè)的回調(diào)函數(shù),這和一般的訂閱/發(fā)布模式不同的地方在于:
基于 Flux 的架構(gòu)思路,Dispatcher.js 提供的 API 很簡(jiǎn)單:
waitFor()
使用dispatcher 只是一個(gè)粘合劑,剩余的 Store、View、Action 就需要按具體需求去實(shí)現(xiàn)了。
接下來結(jié)合 flux-todomvc 這個(gè)簡(jiǎn)單的例子,提取其中的關(guān)鍵部分,看一下實(shí)際應(yīng)用中如何銜接 Flux 整個(gè)流程,希望能對(duì) Flux 各個(gè)部分有更直觀深入的理解。
首先要?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)作的類型名稱常量。
類似 create
、updateText
就是 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)作。
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 就是 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 隔離了。