##Flux與面向組件化開(kāi)發(fā)首先要明確的是,F(xiàn)lux并不是一個(gè)前端框架,而是前端的一個(gè)設(shè)計(jì)模式,其把前端的一個(gè)交互流程簡(jiǎn)單的模擬成了一個(gè)單向數(shù)據(jù)流。在上圖中,我們可以看到Flux的四個(gè)核心構(gòu)成:####Action一個(gè)交互動(dòng)作,來(lái)源于用戶在頁(yè)面組件上的某個(gè)行為,如點(diǎn)擊,失焦,雙擊等等。其往往具有兩個(gè)組成:*交互類型 ,例如創(chuàng)建、刪除、更新等*交互體,或者說(shuō)交互的攜帶信息, 例如創(chuàng)建的文本####Dispatcher分發(fā)器,從上圖的數(shù)據(jù)流中,我們可以看到,用戶產(chǎn)生的一個(gè)交互行為將被送入Dispatcher,分發(fā)器對(duì)Action進(jìn)行簡(jiǎn)單的包裹之后分發(fā)該行為到所有__向其注冊(cè)了的Store__中。>!注意,Dispatcher的這種廣播行為有別于__Pub/Sub__模型,在__Pub/Sub__模型中,需要聲明訂閱的消息類型,然后發(fā)布者會(huì)像訂閱者廣播特定類型的消息。而在Dispatcher中,Store向其注冊(cè)的任意回調(diào)接口都不要聲明訂閱的Action類型,當(dāng)Dispatcher派發(fā)Action時(shí),所有注冊(cè)到Dispatcher的callback都會(huì)得到相應(yīng)。回調(diào)可以通過(guò)簡(jiǎn)單工廠模式(通常是一個(gè)switch塊)來(lái)針對(duì)不對(duì)類型的Action做出不同的行為。####Store數(shù)據(jù)存儲(chǔ)倉(cāng),其保存了我們某個(gè)前端App的數(shù)據(jù),并封裝了對(duì)于數(shù)據(jù)的操作。Store會(huì)向其對(duì)應(yīng)的Dispatcher注冊(cè)一個(gè)回調(diào)函數(shù),其參數(shù)為一個(gè)交互。當(dāng)Action被派發(fā)到Store時(shí),該回調(diào)函數(shù)被調(diào)用,借由Action中描述的__交互類型__,Store進(jìn)行不同處理(一個(gè)簡(jiǎn)單工廠模式),這些處理都將被持久化到Store維護(hù)的數(shù)據(jù)對(duì)象上。Store完成數(shù)據(jù)的變更后,由于Flux并不是雙向數(shù)據(jù)綁定的,所以此時(shí),頁(yè)面組件的數(shù)據(jù)并未得到更新,組件也不會(huì)重新渲染。所以,為了告知組件去更新數(shù)據(jù),Store會(huì)emit一個(gè)變更事件,并且監(jiān)聽(tīng)該事件。當(dāng)監(jiān)聽(tīng)到變更事件產(chǎn)生時(shí),注冊(cè)到這個(gè)事件上的回調(diào)(往往是我們App的狀態(tài)維護(hù)器的狀態(tài)更新函數(shù))會(huì)被調(diào)用,從而更新各個(gè)組件的狀態(tài)。####View顯而易見(jiàn),這就是用戶所能看到的視圖,有別于傳統(tǒng)的MVC,在Flux中,View并不會(huì)和數(shù)據(jù)模型(Model)產(chǎn)生交互,其只會(huì)產(chǎn)生各種交互行為(Actions),這些行為將會(huì)被送到Dispatcher中,如下圖所示:###TODO栗子下面我們分析一個(gè)用React+Flux實(shí)現(xiàn)的一個(gè)Flux栗子,其源碼托管在[github](https://github.com/facebook/flux/tree/master/examples/flux-todomvc)上。在項(xiàng)目實(shí)踐中,面向組件化開(kāi)發(fā)的最佳場(chǎng)景我認(rèn)為是__交互驅(qū)動(dòng)型的開(kāi)發(fā)__,可能描述不夠準(zhǔn)確,準(zhǔn)確點(diǎn)說(shuō)就是一旦一個(gè)完善的交互設(shè)計(jì)稿產(chǎn)生時(shí),我們就可以去__分割__和__分析__組件了,我們現(xiàn)在來(lái)分析Todo的交互原型:>這是交互設(shè)計(jì)師的給我們的原稿,并且,原稿可能遠(yuǎn)不止這樣一幅簡(jiǎn)單的圖像,可能還包括更多的交互效果我們將會(huì)把這個(gè)應(yīng)用拆分為如下組件:####TodoApp通常,在前端面向組件化的開(kāi)發(fā)過(guò)程中,我們往往需要一個(gè)頂部容器包裹住我們的組件,一個(gè)頁(yè)面可以存在若干個(gè)這樣的頂部容器,這個(gè)容器類似一個(gè)集裝箱或者盒子,封裝了某個(gè)頁(yè)面應(yīng)用的所有組件和狀態(tài)。例如,在某視頻網(wǎng)站中,視頻播放窗口可以作為一個(gè)頂部容器,其包裹了播放窗口,進(jìn)度條,播放選項(xiàng)等各個(gè)組件,同時(shí),評(píng)論部分也可以作為一個(gè)頂部容器,其包裹了評(píng)論列表,評(píng)論框等組件。在Todo例子中,TodoApp作為一個(gè)頂部容器,包裹了所有Todo應(yīng)用需要的組件,這樣,我們?cè)趹?yīng)用入口只需要渲染TodoApp就完成了整個(gè)TodoApp的渲染。但更為重要的是,TodoApp將會(huì)封裝其下各個(gè)組件需要用到的狀態(tài),通過(guò)數(shù)據(jù)流,各個(gè)組件將會(huì)收到狀態(tài),并且在狀態(tài)改變時(shí),重新渲染自己,最終更新頁(yè)面內(nèi)容。####Header這是一個(gè)頭部組件,根據(jù)交互設(shè)計(jì),他除了將保有靜態(tài)的“todos”文字標(biāo)題以外,還將會(huì)具有如下行為:*右側(cè)輸入框失焦或者相應(yīng)回車鍵:創(chuàng)建新的任務(wù)####Footer這是一個(gè)底部組件,它將顯示未完成任務(wù)數(shù),并能刪除所有已完成任務(wù),故而,首先他需要獲得如下__狀態(tài)__:*所有任務(wù):*通過(guò)遍歷任務(wù)的完成情況,能獲得未完成任務(wù)數(shù)*通過(guò)遍歷任務(wù)的完成情況,統(tǒng)計(jì)已完成任務(wù)的信息*如果當(dāng)前無(wú)任務(wù),不現(xiàn)實(shí)Footer并且,他具有如下行為:*單擊右側(cè)按鈕(Clear completed): 清除所有已完成任務(wù)------------####MainSection該組件將會(huì)負(fù)責(zé)渲染所有的以創(chuàng)建任務(wù),因而他需要維護(hù)的狀態(tài)為:*所有任務(wù)其具有的行為:*點(diǎn)擊頂部左側(cè)圖標(biāo)按鈕:完成/取消完成所有任務(wù),具體根據(jù)__所有任務(wù)__是否都完成了決定----------------####TodoItem這是Todo項(xiàng),其Todo對(duì)象來(lái)源于MainSection的迭代,并且該組件具有如下行為:*單擊左側(cè)按鈕:完成/取消完成該任務(wù)*單擊右側(cè)按鈕:刪除該Todo*雙擊Todo文本:進(jìn)入如下的編輯模式我們不難發(fā)現(xiàn),“是否處于編輯模式”實(shí)際上可作為該組件的一個(gè)狀態(tài),該狀態(tài)的切花直接影響了該組件的展示和行為,所以,組件應(yīng)當(dāng)維護(hù)一個(gè)狀態(tài):*是否編輯模式在編輯模式中,具有如下行為:*輸入框失焦或者相應(yīng)回車鍵:更新任務(wù)可以看到,在__Header__組件及__TodoItem__組件的輸入框組件具有一致的交互行為,所以,我們可以將其提出來(lái)作為單獨(dú)的組件,這也體現(xiàn)了,一份晚上的交互設(shè)計(jì)原型將預(yù)測(cè)到實(shí)現(xiàn)過(guò)程中的復(fù)用和抽象,避免了一些代碼重構(gòu)的時(shí)間。----------------####TodoTextInput現(xiàn)在,我們抽象出一個(gè)可復(fù)用的輸入組件TodoTextInput,他具有如下行為:*輸入框失焦或者相應(yīng)回車鍵:調(diào)用存儲(chǔ)過(guò)程(創(chuàng)建,更新等等)綜上,我們以一個(gè)簡(jiǎn)單的示意圖表示如上的劃分:上圖__藍(lán)色__橢圓封裝的屬性,__黃色__橢圓封裝的是狀態(tài)。 在每個(gè)TodoItem中,還需要單獨(dú)維護(hù)一個(gè)”是否可編輯狀態(tài)”,該狀態(tài)決定了TodoItem的行為和展示。注意到,因?yàn)開(kāi)_所有任務(wù)__這個(gè)狀態(tài)會(huì)被多個(gè)組件共享(MainSection,F(xiàn)ooter),所以,該狀態(tài)被提到了頂部容器TodoApp中進(jìn)行維護(hù),這樣,通過(guò)TodoApp的__SetState()__方法,所有綁定到TodoApp的組件都獲得了狀態(tài)更新,避免了組件間的相互引用,實(shí)現(xiàn)了組件解耦(唯一的耦合存在于組件與頂層容器),如下圖所示:倘若我們?cè)贛ainSection及Footer中分別維護(hù)這個(gè)狀態(tài),由于MainSection與Footer屬于平級(jí)的組件,所以,當(dāng)MainSection中的__所有任務(wù)__這一狀態(tài)發(fā)生改變時(shí),為使Footer中的狀態(tài)也發(fā)生改變,為此,MainSection及Footer組件都要保存對(duì)方引用,二者將會(huì)是強(qiáng)耦合的,如下圖所示:設(shè)想,如果以后還有更多的組件需要__所有任務(wù)__這一狀態(tài),這一設(shè)計(jì)模式將會(huì)是十分糟糕的,任何一個(gè)組件的脫離將可能導(dǎo)致整個(gè)引用網(wǎng)絡(luò)的崩潰,如下圖所示:###封裝其中app.js為應(yīng)用的入口文件,通常,單頁(yè)面應(yīng)用(SPA)都需要提供一個(gè)最初的文件,然后遞歸渲染DOM樹(shù)。下面,開(kāi)始實(shí)現(xiàn)我們的邏輯,順著Flux的單向數(shù)據(jù)流,逐個(gè)分析Todo例子中的實(shí)現(xiàn)。####Dispatcher__js/AppDispatcher.js__```javascriptvar Dispatcher = require('flux').Dispatcher;module.exports = new Dispatcher();```可以看到,Dispatcher的實(shí)現(xiàn)主要依賴于官方的[flux](https://www.npmjs.com/package/flux)提供支持。我們可以看下flux中的Dispatcher源碼,所有解說(shuō)都放在代碼注釋中:首先看到__Dispatcher__的構(gòu)造函數(shù):```javascriptfunction Dispatcher() {_classCallCheck(this, Dispatcher);this._callbacks = {}; // 保存向Dispatcher注冊(cè)回調(diào)函數(shù)this._isDispatching = false; // 是否正在分派Actionthis._isHandled = {}; // 已經(jīng)完成執(zhí)行的回調(diào)列表this._isPending = {}; // 正在執(zhí)行中的回調(diào)列表this._lastID = 1; // 回調(diào)Id的起始標(biāo)志}```再看注冊(cè)方法__register(callback)__,每個(gè)向Dispatcher的注冊(cè)的回調(diào)(callback)都擁有唯一Id進(jìn)行標(biāo)識(shí):```javascript/*** 向Dispatcher注冊(cè)回調(diào)函數(shù),每個(gè)回調(diào)函數(shù)都有唯一id進(jìn)行標(biāo)識(shí)* @param callback* @returns {string} 注冊(cè)回調(diào)的id*/Dispatcher.prototype.register = function register(callback) {var id = _prefix + this._lastID++;this._callbacks[id] = callback;return id;};/*** 根據(jù)id刪除回調(diào)*/Dispatcher.prototype.unregister = function unregister(id) {!this._callbacks[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.unregister(...): `%s` does not map to a registered callback.', id) : invariant(false) : undefined;delete this._callbacks[id];};```執(zhí)行一個(gè)注冊(cè)了的回調(diào)函數(shù)將經(jīng)歷如下過(guò)程:1.標(biāo)識(shí)當(dāng)前正在執(zhí)行的回調(diào)為進(jìn)行中(Pending)狀態(tài)2.將用戶行為(payload)送回調(diào)執(zhí)行3.執(zhí)行完成,標(biāo)識(shí)該回調(diào)已經(jīng)完成(Handled)```javascript/*** 執(zhí)行回調(diào)函數(shù),該過(guò)程為:* 1. 標(biāo)識(shí)當(dāng)前正在執(zhí)行的回調(diào)為Pending狀態(tài)* 2. 將payload送入回調(diào)執(zhí)行* 3. 執(zhí)行完成,標(biāo)識(shí)該回調(diào)已經(jīng)完成* @internal*/Dispatcher.prototype._invokeCallback = function _invokeCallback(id) {this._isPending[id] = true;this._callbacks[id](this._pendingPayload);this._isHandled[id] = true;};```派發(fā)__dispatch(payload)__指定的用戶行為payload到所有的callback將經(jīng)歷如下過(guò)程:首先,需要明確的是能夠進(jìn)行派發(fā)的前提是當(dāng)前Dispatcher為空閑狀態(tài),接下來(lái)1.派發(fā)前的預(yù)處理___startDispatching()__1.初始化所有回調(diào)的狀態(tài)2.設(shè)置當(dāng)前正在分發(fā)的payload3.標(biāo)識(shí)當(dāng)前的Dispatcher狀態(tài)為"正在進(jìn)行派發(fā)"2.根據(jù)注冊(cè)順序依次執(zhí)行回調(diào)___invokeCallback(id)__3.派發(fā)結(jié)束后的收尾工作___stopDispatching()__1.清除派發(fā)對(duì)象2.標(biāo)識(shí)當(dāng)前的Dispatcher狀態(tài)為"結(jié)束派發(fā)"```javascript/*** 派發(fā)一個(gè)payload到所以已注冊(cè)的callback中*/Dispatcher.prototype.dispatch = function dispatch(payload) {!!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.') : invariant(false) : undefined;this._startDispatching(payload);try {for (var id in this._callbacks) {if (this._isPending[id]) {continue;}this._invokeCallback(id);}} finally {this._stopDispatching();}};/*** 分發(fā)payload前的初始化:* 1. 初始化所有回調(diào)的狀態(tài)* 2. 設(shè)置當(dāng)前正在分發(fā)的payload* 3. 標(biāo)識(shí)當(dāng)前"正在進(jìn)行派發(fā)"* @internal*/Dispatcher.prototype._startDispatching = function _startDispatching(payload) {for (var id in this._callbacks) {this._isPending[id] = false;this._isHandled[id] = false;}this._pendingPayload = payload;this._isDispatching = true;};/*** 結(jié)束派發(fā)時(shí)的收尾工作* 1. 清除派發(fā)對(duì)象* 2. 標(biāo)識(shí)當(dāng)前"結(jié)束派發(fā)"* @internal*/Dispatcher.prototype._stopDispatching = function _stopDispatching() {delete this._pendingPayload;this._isDispatching = false;};```#####waitFor再看Dispatcher中一個(gè)很重要的方法:__waitFor(ids)__, 顧名思義,該方法的作用是等待指定的回調(diào)的函數(shù)調(diào)用完成。因而,該方法主要保證了回調(diào)函數(shù)的執(zhí)行的順序性。例如,在一個(gè)航班訂票系統(tǒng)中,我們首先要選擇完國(guó)家(Country),才能選擇城市(City),所以,當(dāng)一個(gè)類型為“更新選擇國(guó)家”的交互被送到CityStore所注冊(cè)的回調(diào)時(shí),為了保證能正確的選擇更新后國(guó)家的城市```javascriptCityStore.dispatchToken = flightDispatcher.register(function(payload) {if (payload.actionType === 'country-update') {/** 如果不執(zhí)行waitFor(),那么可同CityStore的回調(diào)先于ContryStore的回調(diào)執(zhí)行* 此時(shí)的國(guó)家尚未更新,得到的默認(rèn)城市是錯(cuò)誤的,而并不是最新的* */flightDispatcher.waitFor([CountryStore.dispatchToken]);// waitFor()保證了ContryStore先響應(yīng)了'country-update',即保證了國(guó)家更新先于城市更新// 此時(shí)我們能正確的選擇該國(guó)家的城市CityStore.city = getDefaultCityForCountry(CountryStore.country);}});```下面我們看__waitFor()__的源碼實(shí)現(xiàn):```javascript/*** 等待指定的回調(diào)完成*/Dispatcher.prototype.waitFor = function waitFor(ids) {!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): Must be invoked while dispatching.') : invariant(false) : undefined;for (var ii = 0; ii < ids.length; ii++) {var id = ids[ii];if (this._isPending[id]) {!this._isHandled[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): Circular dependency detected while ' + 'waiting for `%s`.', id) : invariant(false) : undefined;continue;}!this._callbacks[id] ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatcher.waitFor(...): `%s` does not map to a registered callback.', id) : invariant(false) : undefined;this._invokeCallback(id);}};```####Store實(shí)現(xiàn)在__js/stores/TodoStore.js__中:首先,我們維護(hù)我們的數(shù)據(jù)對(duì)象,并提供若干對(duì)于該數(shù)據(jù)的操作:```javascript// 保存TODO列表var _todos = {};/*** 創(chuàng)建一個(gè) Todo* @param text {string} Todo內(nèi)容*/function create(text) {// ...}/*** 更新一個(gè) TODO item* @param id {string}* @param updates {object} 待更新對(duì)象的屬性*/function update(id, updates) {// ...}/*** 根據(jù)一個(gè)更新屬性值對(duì)象更新所有 Todo* @param updates {object}*/function updateAll(updates) {// ...}/*** 刪除 Todo* @param id {string}*/function destroy(id) {// ...}/*** 刪除所有的已完成的 TODO items*/function destroyCompleted() {// ...}```然后導(dǎo)出一個(gè)全局單例,該單例提供了常用的外部訪問(wèn)接口,并且通過(guò)node提供的EventEmitter來(lái)實(shí)現(xiàn)事件的派發(fā)和監(jiān)聽(tīng):```javascriptvar TodoStore = assign({}, EventEmitter.prototype, {/*** 是否所有TODO 都已完成* @return {boolean}*/areAllComplete: function () {// ...},/*** 獲得所有的TODO* @returns {object}*/getAll: function () {// ...},/*** 發(fā)送變更事件*/emitChange: function () {// ...},/*** 添加變更事件監(jiān)聽(tīng)* @param callback*/addChangeListener: function (callback) {// 一旦受到變更事件, 觸發(fā)回調(diào)/**? 例如, 當(dāng)我們創(chuàng)建一條todo時(shí),*? TodoStore將會(huì)發(fā)出一條變更事件,*? 上游的狀態(tài)維護(hù)器將會(huì)調(diào)用callback進(jìn)行狀態(tài)更新*/this.on(CHANGE_EVENT, callback);},/*** 刪除變更事件監(jiān)聽(tīng)* @param callback*/removeChangeListener: function (callback) {this.removeListener(CHANGE_EVENT, callback);}});```最后,我們需要向__AppDispatcher__注冊(cè)回調(diào)函數(shù),以便在payload被分發(fā)到TodoStore時(shí),TodoStore能做出相應(yīng):```javascriptAppDispatcher.register(function callback(action) {var text;// 根據(jù)不同的action類型(即不同的交互邏輯), 執(zhí)行不同過(guò)程switch (action.actionType) {case TodoConstants.TODO_CREATE:text = action.text.trim();if( text!=='') {create(text);// 一旦變更,發(fā)出變更事件,TodoStore.emitChange();}break;case TodoConstants.TODO_TOGGLE_COMPLETE_ALL:// ...break;case TodoConstants.TODO_UNDO_COMPLETE:// ...break;case TodoConstants.TODO_COMPLETE:// ...break;case TodoConstants.TODO_UPDATE_TEXT:// ...break;case TodoConstants.TODO_DESTROY:// ...break;case TodoConstants.TODO_DESTROY_COMPLETED:// ...break;default:// no op}});```>!注意, 在回調(diào)執(zhí)行過(guò)程中,如果發(fā)生狀態(tài)的變動(dòng),需要發(fā)出變更事件,以便上游注冊(cè)的回調(diào)函數(shù)能夠獲得相應(yīng)并更新?tīng)顟B(tài)到下游。####Actions我們將TodoApp中常見(jiàn)的Action都封裝到了__js/TodoActions.js__中, 通過(guò)其中的__AppDispatcher__單例,我們可以將Action派發(fā)出去:```javascriptvar TodoActions = {/*** 創(chuàng)建行為* @param text {string}*/create: function (text) {// 將創(chuàng)建行為送到Dispatcher, Dispatcher派發(fā)這個(gè)行為(action對(duì)象)到各個(gè)StoreAppDispatcher.dispatch({actionType: TodoConstants.TODO_CREATE,text: text});},/*** 更新行為* @param id {string}* @param text {string}*/updateText: function (id, text) {// ...},/*** 全部設(shè)置為完成* @param todo*/toggleComplete: function (todo) {// ...},/*** 標(biāo)記所有的Todo為已完成*/toggleCompleteAll: function () {// ...},/**** @param id*/destroy: function (id) {// ...},/*** 刪除所有已完成的Todo*/destroyCompleted: function() {// ...}};```####Components下面開(kāi)始實(shí)現(xiàn)各個(gè)組件, 個(gè)人偏向的流程是先在組件目錄下創(chuàng)建好各個(gè)組件文件,并以如下內(nèi)容先導(dǎo)出,亦即,我們先創(chuàng)建空白組件,之后再依序進(jìn)行裝填```javascriptvar React = require('react');var Header = React.createClass({render: function () {// TODO::render},});module.exports = Header;```裝填順序我會(huì)選擇先裝填頂部容器(此例中即為_(kāi)_TodoApp__),之后按照DOM樹(shù)自底向上的進(jìn)行裝填:__TodoApp.react.js__:```javascriptvar Footer = require('./Footer.react');var Header = require('./Header.react');var MainSection = require('./MainSection.react');var React = require('react');var TodoStore = require('../stores/TodoStore');// 在根DOM下維護(hù)狀態(tài),// 這樣的狀態(tài)往往是共享狀態(tài)(會(huì)向下傳遞的狀態(tài))function getTodoState() {return {allTodos: TodoStore.getAll(),areAllComplete: TodoStore.areAllComplete()};}var TodoApp = React.createClass({getInitialState: function () {return getTodoState();},/*** 綁定生命期--掛載*/componentDidMount: function () {// 掛載時(shí)再為TodoStore添加監(jiān)聽(tīng)器TodoStore.addChangeListener(this._onChange);},componentWillUnmount: function () {TodoStore.removeChangeListener(this._onChange);},render: function () {return (
);},/*** Event handler for 'change' events coming from the TodoStore*/_onChange: function() {this.setState(getTodoState());}});module.exports = TodoApp;```>為了方便,TodoApp不僅維護(hù)__allTodos(所有任務(wù))__這個(gè)狀態(tài),還維護(hù)__areAllComplete(是否所有任務(wù)都已完成)__,該狀態(tài)主要服務(wù)于__MainSection__中的---”完成所有/取消完成所有任務(wù)“這一用例,避免重復(fù)遍歷__allTodos__的開(kāi)銷。我們可以看到,TodoApp提供了一個(gè)___onChange()__方法作為TodoStore的__change__事件的回調(diào),當(dāng)TodoStore發(fā)出change事件時(shí),TodoApp將刷新?tīng)顟B(tài),借此通知其下組件如MainSection等重新渲染。通過(guò)這樣一個(gè)頂層組件,我們不用把對(duì)Store的事件監(jiān)聽(tīng)和俘獲進(jìn)行集中化處理,避免在更多的組件的中監(jiān)聽(tīng)Store的事件。更多組件的實(shí)現(xiàn)不再贅述。下面著重介紹flux的工作流程####工作流程我們以__創(chuàng)建新的Todo__這一工作流程為例展示Flux的工作過(guò)程。在Flux中,該流程如下圖所示:1.我們?cè)趧?chuàng)建Todo的輸入框中敲入數(shù)據(jù),在輸入框上,我們監(jiān)聽(tīng)了__失焦(onBlur)__和__按下鍵盤按鍵(onKeyDown)__的事件```javascript// js/components/TodoTextInput.react.js/*** @return {object}*/render: function() /*object*/ {return ();},```當(dāng)事件發(fā)生時(shí),調(diào)用___save()__方法進(jìn)行處理:```_save: function() {this.props.onSave(this.state.value);this.setState({value: ''});},```2.注意,我們通過(guò)給__TodoTextInput__設(shè)定__onSave__屬性來(lái)指定事件發(fā)生后的回調(diào),在__Header__組件中,我們通過(guò)屬性指定了這個(gè)回調(diào),使得我們?cè)谑Ы够蚧剀嚢聪潞螅軌蛳馜ispatch請(qǐng)求派發(fā)(dispatch)一個(gè)__“創(chuàng)建行為”__```javascript// js/components/Header.react.js/*** @return {object}*/render: function() {return (無(wú)標(biāo)題文章
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
推薦閱讀更多精彩內(nèi)容
- Flux與面向組件化開(kāi)發(fā) 首先要明確的是,F(xiàn)lux并不是一個(gè)前端框架,而是前端的一個(gè)設(shè)計(jì)模式,其把前端的一個(gè)交互流...
- 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
- React創(chuàng)建組件的三種方式及其區(qū)別 React推出后,出于不同的原因先后出現(xiàn)三種定義react組件的方式,殊途同...
- WebView·開(kāi)車指南 2016-08-31BugDev 北京市東城區(qū)首席Bug布道師開(kāi)山之作,一整月交通事故血...