- flux 的核心思想是中心化控制,它讓所有的請求與改變都只能通過 action 發出,統一 由 dispatcher 來分配。好處是 View 可以保持高度簡潔,它不需要關心太多的邏輯,只需要關心傳入的資料。中心化還控制了所有資料,發生問題時可以方便查詢。
- flux 缺點也很明顯,層級太多,需要重複寫太多的冗餘程式碼。
- Flux_GitHub地址
一個 Flux 應用包含四個部分:
- Dispatcher,處理動作分發,維持 Store 之間的依賴關係
- Store,負責儲存資料和處理資料相關邏輯
- Action,觸發 Dispatcher
- View,檢視,負責顯示使用者介面
- 通過上圖可以看出來,Flux 的特點就是單向資料流:
- 使用者在 View 層發起一個 Action 物件給 Dispatcher
- Dispatcher 接收到 Action 並要求 Store 做相應的更改
- Store 做出相對應更新,然後發出一個 change 事件
- View 接收到 change 事件後,更新頁面
簡單圖示對比MVC
- 基本的MVC資料流
- 複雜的MVC資料流
- 基本的Flux資料流
- 複雜的Flux資料流
相比MVC模式,Flux多出了更多的箭頭跟圖示,但是有個關鍵性的差別是:所有的剪頭都指向一個方向,在整個系統中形成一個閉環。
模式的演變
- 與其說Flux是MVC模式的顛覆,不如說是對MVC模式的創新
- 傳統的MVC模式中,View對Model直接修改的方式非常直截了當,適合小型web應用,然而一但web應用中存在多個Model,多個View,那麼Model和View之間的決定關係就可能變得混亂,難以駕馭,並且這種模式阻礙了Model和View的元件化拆分。
- 而對比上面兩個複雜模式下的對比圖,我們發現MVC模式下真正的痛點在於: 缺少了一個和使用者互動行為有關的action抽象
- 從程式碼層面而言,flux無非就是一個常見的event dispatcher,其目的是要將以往MVC中各個View元件內的controller程式碼片斷提取出來放到更加恰當的地方進行集中化管理,並從開發體驗上實現了舒適清爽、容易駕馭的“單向流”模式,並且在這種排程模式下面,事情的變化變得清晰可預測。
Flux原始碼簡析
"shut up and show me the code"
這裡主要分析Dispatcher檔案程式碼
複製程式碼
Dispatcher檔案初始化
var invariant = require('invariant');
export type DispatchToken = string;
var _prefix = 'ID_';
class Dispatcher<TPayload> {
_callbacks: {[key: DispatchToken]: (payload: TPayload) => void};
_isDispatching: boolean;
_isHandled: {[key: DispatchToken]: boolean};
_isPending: {[key: DispatchToken]: boolean};
_lastID: number;
_pendingPayload: TPayload;
constructor() {
this._callbacks = {};
this._isDispatching = false;
this._isHandled = {};
this._isPending = {};
this._lastID = 1;
}
register(callback: (payload: TPayload) => void): DispatchToken {
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
return id;
}
...
}
複製程式碼
- 程式碼是擷取過的,這裡主要是註冊一個DispatchToken函式,字首'ID_'加上自增的++確保唯一(一個store對應多個id應該也是可行的)
- callbacks,就是DispatchToken和函式回撥的一個Dictionary。
- isDispatching,體現當前Dispatcher是否處於dispatch狀態。
- isHandled,通過token去檢測一個函式是否被處理過了。
- isPending,通過token去檢測一個函式是否被提交Dispatcher了。
- lastID,最近一次被加入Dispatcher的函式體的UniqueID,即DispatchToken。
- pendingPayload,需要傳遞給呼叫函式的引數。
dispatch方法
dispatch(payload: TPayload): void {
invariant(
!this._isDispatching,
'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'
);
this._startDispatching(payload);
try {
for(var id in this._callbacks) {
if (this._isPending[id]) {
continue;
}
this._invokeCallback(id);
}} finally {
this._stopDispatching();
}}
isDispatching(): boolean {
return this._isDispatching;
}
_invokeCallback(id: DispatchToken): void {
this._isPending[id] = true;
this._callbacks[id](this._pendingPayload);
this._isHandled[id] = true;
}
_startDispatching(payload: TPayload): void {
for(var id in this._callbacks) {
this._isPending[id] = false;
this._isHandled[id] = false;
}
this._pendingPayload = payload;
this._isDispatching = true;
}
_stopDispatching(): void {
delete this._pendingPayload;
this._isDispatching = false;
}
複製程式碼
- 主要是判斷函式是否處於pending狀態,將非pending狀態的callback通過_invokeCallback執行,所有執行完了以後,通過_stopDispatching恢復狀態。
- _startDispatching函式的作用,是將所有註冊的callback的狀態都清空,並標記Dispatcher的狀態進入dispatching
- _invokeCallback函式很簡單,當真正呼叫callback之前將其狀態設定為pending,執行完成之後設定為handled
waitFor方法
waitFor(ids: Array<DispatchToken>): void {
invariant(
this._isDispatching,
'Dispatcher.waitFor(...): Must be invoked while dispatching.'
);
for (var ii = 0; ii < ids.length; ii++) {
var id = ids[ii];
if (this._isPending[id]) {
invariant(
this._isHandled[id],
'Dispatcher.waitFor(...): Circular dependency detected while ' +
'waiting for `%s`.',
id
);
continue;
}
invariant(
this._callbacks[id],
'Dispatcher.waitFor(...): `%s` does not map to a registered callback.',
id
);
this._invokeCallback(id);
}
}
複製程式碼
- 簡單的認為,dispatch方法對回撥的遍歷是簡單、同步式的。當在執行callback過程中遇到waifFor方法時,對當前callback的呼叫就會中斷,wairFor方法會根據宣告的依賴重新確定遍歷順序,當所有依賴都被執行後,原先中止的callback才會繼續執行。
- ps:下面這段話來自於複製(權當參考)
- 首先是一個Invariant判斷當前必須處於Dispatching狀態。一開始比較難理解,簡單來說就是,如果不處於Dispatching狀態中,那麼說明壓根沒有函式在執行,那你等誰呢?
- 然後該函式從DispatchToken的陣列中進行遍歷,如果遍歷到的DispatchToken處於pending狀態,就暫時跳過他。
- 但是,在這有個必要的迴圈以來的檢查,試想如下情況,如果A函式以來B的Token, B函式依賴A的Token,就會造成“死鎖”。所以,當一個函式依賴的物件處於pending,說明這個函式已經被開始執行了,但是如果同時該函式沒有進入handled狀態,說明該函式也被卡死了。
- 檢查token對應的callback是否存在,呼叫這個token對應的函式。
- ps: 這裡面的部分圖片以及說明來自於第三方,但是想不起來了?