詳解React Flux架構工作方式
什么是Flux
Flux是非死book內部用來構建React應用的一套架構。它本身并不是一個框架或庫。它僅僅是一個用于完善React應用開發的一種新的應用程序架構,Flux架構最大的特點是其倡導的單向數據流方案。
非死book還提供了Dispatcher的開源庫。你可以將Dispatcher認為是一種全局pub/sub系統的事件處理器,用于向所注冊的回調函數廣播payloads。通常情況下,如果你使用Flux架構,你可以借助于非死book提供的這個Dispatcher實現,并且可以借助NodeJS中的EventEmitter模塊來配置事件系統, 從而幫助管理應用程序的狀態。
組成結構
Flux主要包括下面四個獨立的組件,下面簡單地解釋下:
- Actions:幫助向Dispatcher傳遞數據的輔助方法;
- Dispatcher:接收action,并且向注冊的回調函數廣播payloads;
- Stores:應用程序狀態的容器&并且含有注冊到Dispatcher的回調函數;
- Controller Views:React組件,從Store獲取狀態,并將其逐級向下傳遞給子組件。 </ul>
讓我們通過一幅圖來簡單看一下這四個部分的關系:
如何處理外部API
但是,很多場景下不僅僅包括應用程序內部的狀態數據,還可能包括來自外部的數據,也就是如果我們需要消費RESTful服務,那么我們該如何將這些外部數據引入到Flux架構的單向數據流中呢?經過實踐,我們發現,在Action中引入外部數據流是個不錯的實踐, 數據然后再被送到Store中。
在繼續閱讀之前,我強烈建議你閱讀下非死book提供的Flux官方示例程序的 源代碼。在你簡單閱讀了這個例子的源代碼后,我們再來詳細的探討Flux中這幾個組件的作用:
Dispatcher
我們首先來看看Dispatcher的作用:你可以將Dispatcher看成是Flux架構中整個數據流程的管理者。或者你可以將它看成你整個應用的中央交換機。Dispatcher接收actions作為payloads,并且將payloads分發給所注冊的回調函數。
那么它就是一個pub/sub嗎?
不完全是。Dispatcher會將payload廣播給所有向它注冊的回調函數,并且包括了允許你以指定次序調用這些回掉函數的執行邏輯,例如在處理前等待數據更新。在Flux架構中只有一個Dispatcher,它是你整個應用的中央Hub。我們來創建一個簡單的Dispatcher:
var Dispatcher = require('flux').Dispatcher; var AppDispatcher = new Dispatcher();AppDispatcher.handleViewAction = function(action) { this.dispatch({ source: 'VIEW_ACTION', action: action }); }
module.exports = AppDispatcher;</pre>
在上面的代碼中,我們創建了一個Dispatcher的實例,它包括了一個叫做handleViewAction的方法。創建這個方法的目的是為了讓你能夠輕松的區分, 是視圖層觸發的action,還是服務器/API觸發的action。
在handleViewAction方法中調用了dispatch方法(該方法來自Dispatcher實現),它會將payloads廣播給所有注冊到它的回調函數 (注意,這里所說的回調是在store中注冊給Dispatcher的)。然后,action會觸發Store中相應事件,并最終體現在state的更新上。
可以用下面這張圖來表示這個過程:
![]()
依賴
非死book所提供的Dispatcher模塊還有一個非常贊的功能,那就是它能夠定義依賴,并在Store中向Dispatcher注冊回調函數。 因此,如果你的應用的某個部分依賴于其他部分的數據更新,為了能夠進行合適的渲染,Dispatcher中的waitFor方法將會非常有用。
為了能夠利用這一特性,我們需要在Store中存儲注冊給Dispatcher的回調函數的返回值,這里,我們命名為dispatcherIndex:
ShoeStore.dispatcherIndex = AppDispatcher.register(function(payload) {});</pre>
然后在Store中處理每一個被分發的action時,我們可以使用Dispatcher的waitFor方法來確保我們的ShoeStore已經被更新了,示例代碼如下:
case 'BUY_SHOES': AppDispatcher.waitFor([ ShoeStore.dispatcherIndex ], function() { CheckoutStore.purchaseShoes(ShoeStore.getSelectedShoes()); }); break;
Stores
在Flux中,可以用Store來分領域的管理應用程序的狀態,例如ShoeStore、AppStore等。從一個更高層角度來看,可以簡單地認為應用的每一個部分可以對應于一個Store,Store可以用來管理數據,定義數據檢索方法,此外,Store中還包括了注冊給Dispatcher的回調函數。
下面我們來看一個簡單的Store的例子:
var AppDispatcher = require('../dispatcher/AppDispatcher'); var ShoeConstants = require('../constants/ShoeConstants'); var EventEmitter = require('events').EventEmitter; var merge = require('react/lib/merge');// Internal object of shoes var _shoes = {};
// Method to load shoes from action data function loadShoes(data) { _shoes = data.shoes; }
// Merge our store with Node's Event Emitter var ShoeStore = merge(EventEmitter.prototype, {
// Returns all shoes getShoes: function() { return _shoes; },
emitChange: function() { this.emit('change'); },
addChangeListener: function(callback) { this.on('change', callback); },
removeChangeListener: function(callback) { this.removeListener('change', callback); }
});
// Register dispatcher callback AppDispatcher.register(function(payload) { var action = payload.action; var text; // Define what to do for certain actions switch(action.actionType) { case ShoeConstants.LOAD_SHOES: // Call internal method based upon dispatched action loadShoes(action.data); break;
default: return true;
}
// If action was acted upon, emit change event ShoeStore.emitChange();
return true;
});
module.exports = ShoeStore;</pre>
以上代碼可能做的最重要的一件事就是:我們使用NodeJS中的EventEmitter模塊來擴展我們的Store。這使得我們的Store能夠監聽和廣播事件。這也使得我們的視圖/組件能夠基于這些事件來更新。 這是因為我們的控制器視圖(Controller View)會監聽我們的Store, 并利用這一點通過發出事件方式通知控制器視圖應用程序的狀態發生了改變, 從而進行視圖層的重新渲染。
在Store中還有一個值得注意的事,那就是我們使用Dispatcher的regiter方法來注冊回調函數。 這意味著,我們創建的Store會監聽來自AppDispatcher的廣播,這里我們使用switch語句來 區分廣播內容所對應的action。如果一個相關的action發生了,那么就觸發一個change事件,并且監聽 此事件的視圖會根據新的狀態(state)進行重新渲染。這個過程如下圖所示。
![]()
在上面的代碼中,ShoeStore中還包括一個公共方法getShoes(),它可以在控制器視圖中被調用, 用于檢索所有在_shoes對象中的數據,并且使用這個數據作為組件的狀態數據。因為這是個非常簡單 的例子,在實際應用開發中你可以使用更加復雜的數據處理邏輯。
Action創建器和Actions
行為創建器(Action Creators)是一組會在視圖中被調用的方法(也可以在其他地方使用), 它們用于向Dispatcher發送actions。也就是說,我們使用Dispatcher分發的payloads,實際上就是actions。
在非死book提供的例子中,action的類型常數被用于定義這些action所被應用的場景,并且是伴隨著action一起被傳遞的。現在,在所注冊的回調函數內部,這些actions可以根據他們的action類型來處理。
我們可以看一個簡單的類型常數的定義:
var keyMirror = require('react/lib/keyMirror');module.exports = keyMirror({ LOAD_SHOES: null });</pre>
在上面的代碼中,我們用了React的keyMirror庫來生成我們的類型常數的Key/Value對。如果僅僅是看這個文件,我們大致可以猜到我們的應用會加載鞋子(shoes)。借助于類型常數,可以使得應用 中涉及到的事物變得更加地可組織,可以讓開發者能通過這樣的高層抽象來組織應用程序。
現在,讓我們來大致的看一個對應的行為創建器(action creator)的定義:
var AppDispatcher = require('../dispatcher/AppDispatcher'); var ShoeStoreConstants = require('../constants/ShoeStoreConstants');var ShoeStoreActions = {
loadShoes: function(data) { AppDispatcher.handleAction({ actionType: ShoeStoreConstants.LOAD_SHOES, data: data }) }
};
module.exports = ShoeStoreActions;</pre>
在上面的代碼中,我們在定義了ShoeStoreActions對象,并在其中定義了loadShoes()方法, 該方法內調用了來自AppDispatcher的handleViewAction方法,并且將相應的行為類型與 我們提供的數據相關聯。現在,我們可以在我們的視圖代碼或API中引入這個action文件。我們可以調用ShoeStoreActions.loadShoes(ourData)方法來向Dispatcher發送payload,隨后Dispatcher會將它廣播出去。ShoeStore則會監聽來自Dispatcher的廣播,并且觸發相應的事件來加載相應的鞋子。
控制器視圖 Controller View
你可以簡單的將控制器視圖理解為React組件,這些組件會監聽change事件,如果事件發生了, 就從Stores中獲取應用程序新的狀態數據。隨后,可以將數據通過props逐級向子組件傳遞。這個過程大致如下圖所示:
![]()
讓我們來看看代碼長啥樣:
/* @jsx React.DOM /var React = require('react'); var ShoesStore = require('../stores/ShoeStore');
// Method to retrieve application state from store function getAppState() { return { shoes: ShoeStore.getShoes() }; }
// Create our component class var ShoeStoreApp = React.createClass({
// Use getAppState method to set initial state getInitialState: function() { return getAppState(); },
// Listen for changes componentDidMount: function() { ShoeStore.addChangeListener(this._onChange); },
// Unbind change listener componentWillUnmount: function() { ShoesStore.removeChangeListener(this._onChange); },
render: function() { return ( <ShoeStore shoes={this.state.shoes} /> ); },
// Update view state when change event is received _onChange: function() { this.setState(getAppState()); }
});
module.exports = ShoeStoreApp;</pre>
在上面的代碼中,我們使用addChangeListener來監聽change事件,并且在接收到事件后更新應用程序的狀態數據。
我們的應用程序狀態數據存儲在Store中,因此我們可以用Store中的公共方法來獲取數據,然后將其設置為應用程序的狀態。
完整的數據流程
前面我們依次解讀了Flux架構中的每一個獨立部分,我們有了對Flux架構中的每一個組件的一個大致的認識, 并且能大致的了解了Flux架構的工作流。在這之前,我們已經通過了幾張圖片描述了數據在組件之間的流向, 現在讓我們將上面的子流程組合起來,以更好的理解Flux的數據流:
![]()
小結
在讀完這篇文章后,如果你之前并不了解非死book的Flux架構,那么希望你現在能夠說理解了。 但另一方面,如果不在React的實際編程開發中使用這一架構,你是很難真正的體會到Flux架構的 特點的。
當你使用Flux后,你會發現沒有Flux的React應用開發就相當于是沒有jQuery的DOM遍歷操作。也就是說, 沒有Flux你可以照常進行React應用開發,但是你在代碼組織和數據流程上會缺乏優雅的解決方案。
另一個方面,如果你想使用FLux架構,但你并不想使用React,那么你可以參考 Delorean,它是一個Flux框架,并且可以讓你使用Ractive.js或者Flight。另一個值得參考的庫是 Fluxxor,它實現了在緊耦合的Flux組件中包含一個中央Flux實例。
最后,再次說明,如果要真正的理解Flux,你就得在實際開發中體驗它。
原文鏈接:Getting To Know Flux, the React.js Architecture
譯者簡介:景莊,前端工程師,關注Node.js、前端工程化。個人博客: http://wwsun.github.com。
來自: http://www.csdn.net/article/2015-08-31/2825587-react-flux