翻譯:莫銘
原文地址:Flux-In Depth Overview
Flux是Facebook用來(lái)構(gòu)建客戶端web應(yīng)用的應(yīng)用框架。它使用單向數(shù)據(jù)流來(lái)補(bǔ)充React的可組合視圖組件。它更像是一種模式,而不是一個(gè)正式的框架,你可以立即開始使用Flux,而不需要大量的新代碼。
Flux應(yīng)用有三個(gè)主要部分: dispatcher, stores, 和views(React 組件)。不要將他們和Model-View-Controller混為一談。Controllers在Flux應(yīng)用中是不存在的,他們以controller-views的形式存在(就是一種view,常見(jiàn)于層次結(jié)構(gòu)的頂層,他們從stores接收數(shù)據(jù),然后將數(shù)據(jù)向下傳給他們的子孫窗體)。另外,action的生成器(dispatcher的helper函數(shù))作為語(yǔ)義API用來(lái)描述應(yīng)用中可能出現(xiàn)的變化)。為方便理解也可以將它們視為Flux更新循環(huán)中的第四部分。
Flux避開MVC,而更傾向于單向數(shù)據(jù)流。當(dāng)用于與React view交互時(shí),view會(huì)通過(guò)dispatcher將action傳遞給每個(gè)store(這些store持有應(yīng)用的數(shù)據(jù)和業(yè)務(wù)邏輯,并且更新那些受影響的views)。 這與React的聲明式編程風(fēng)格特別相似,它允許store發(fā)送更新,而無(wú)需指定如何在states之間轉(zhuǎn)換views。
我們從恰當(dāng)?shù)靥幚砼缮鷶?shù)據(jù)開始:比如,我們想顯示消息線程的未讀信息數(shù),而另一個(gè)視圖顯示線程列表,高亮顯示出那些有未讀消息的線程。使用MVC處理這種情況就比較困難(標(biāo)記一個(gè)線程可讀,將會(huì)更新線程模型,進(jìn)而需要更新未讀數(shù)模型)。這些依賴和級(jí)聯(lián)更新在大型的MVC應(yīng)用中經(jīng)常發(fā)生,致使數(shù)據(jù)流錯(cuò)綜復(fù)雜,以及不可預(yù)測(cè)的結(jié)果。
Control和stores是相反的:stores接收更新,并根據(jù)需要進(jìn)行調(diào)整,而不是依賴外部以固定的方式來(lái)更新它的數(shù)據(jù)。只有store自己(內(nèi)部)才知道如何管理它所管理的數(shù)據(jù),這有助于保持清晰的職責(zé)劃分。Stores沒(méi)有直接的setter函數(shù)(比如setAsRead()),取而代之的是在dispatcher中注冊(cè)回調(diào)函數(shù),來(lái)獲取新的數(shù)據(jù)。
結(jié)構(gòu)與數(shù)據(jù)流
Flux應(yīng)用中的數(shù)據(jù)單向流動(dòng):

單向數(shù)據(jù)流是Flux模式的核心,上圖應(yīng)該是Flux程序員心中主要的模型。dispatcher,stores和views是具有清晰輸入和輸出的獨(dú)立節(jié)點(diǎn)。actions是一個(gè)簡(jiǎn)單的objects,包含新數(shù)據(jù)和一個(gè)標(biāo)識(shí)類型的屬性。
views會(huì)根據(jù)用戶的交互,生成一個(gè)新的action在系統(tǒng)中傳播:

所有數(shù)據(jù)流經(jīng)作為中央集線器的dispatcher。Actions通常來(lái)自于用戶與視圖的交互,并且在一個(gè)action創(chuàng)建器函數(shù)中提交給dispatcher。然后dispatcher調(diào)用store已經(jīng)在dispatcher中注冊(cè)好的回調(diào)函數(shù),store會(huì)回應(yīng)那些與它維護(hù)的state相關(guān)的actions。然后,stores會(huì)發(fā)出一個(gè)更改事件,來(lái)告知controller-views,數(shù)據(jù)層發(fā)生了變化。Controller-views監(jiān)聽(tīng)這些事件,并在一個(gè)事件處理器(event handler)中從store獲取數(shù)據(jù)。controller-views調(diào)用他們自己的setState()函數(shù),引起自身以及組件樹中自己的后代的重新渲染。

這種結(jié)構(gòu)讓我們很容易理解我們的應(yīng)用,以功能反應(yīng)式編程或者數(shù)據(jù)流式編程的方式,數(shù)據(jù)在應(yīng)用中以固定的方向流動(dòng),而不是雙向綁定。應(yīng)用state只在store中維護(hù),從而使應(yīng)用的不同部分保持高度解耦。依賴只發(fā)生在store之間,通過(guò)dispatcher管理的同步更新,確保他們被留在嚴(yán)格的層次結(jié)構(gòu)中。
我們發(fā)現(xiàn)雙向數(shù)據(jù)綁定會(huì)導(dǎo)致級(jí)聯(lián)更新,也就是改變一個(gè)對(duì)象,將會(huì)導(dǎo)致另一個(gè)對(duì)象的改變,進(jìn)而觸發(fā)更多的更新。隨著應(yīng)用規(guī)模的增長(zhǎng),這些級(jí)聯(lián)更新將會(huì)導(dǎo)致用戶交互結(jié)果的不可預(yù)測(cè)。但如果更新只會(huì)在一輪內(nèi)更改數(shù)據(jù),整個(gè)系統(tǒng)就會(huì)變得更加可預(yù)測(cè)。
讓我們仔細(xì)看看Flux的各個(gè)部分。dispatcher是一個(gè)好的切入點(diǎn)。
單獨(dú)的Dispatcher
在一個(gè)Flux應(yīng)用中,dispatcher是管理數(shù)據(jù)流的中央樞紐。它并沒(méi)有真實(shí)的智能,本質(zhì)上只是一個(gè)回調(diào)到store的注冊(cè)表。每個(gè)store注冊(cè)自己,并提供一個(gè)回調(diào)函數(shù)。當(dāng)一個(gè)action創(chuàng)建器將一個(gè)新的action提供給dispatcher,應(yīng)用中所有的stores,通過(guò)注冊(cè)的回調(diào)函數(shù)接收到該action。
隨著應(yīng)用規(guī)模的增長(zhǎng),dispatcher變得更為重要,因?yàn)樗梢酝ㄟ^(guò)這些注冊(cè)的回調(diào)函數(shù),不同的調(diào)用順序,來(lái)管理stores間的依賴關(guān)系。store可以聲明式的等待其他store完成更新后,再相應(yīng)的更新自己。
Facebook在產(chǎn)品用用到的同款dispatcher現(xiàn)在可通過(guò)npm, Bower或GitHub獲得。
Stores
Stores擁有應(yīng)用的state和邏輯。他們的角色有點(diǎn)類似于傳統(tǒng)MVC中的模型model,但是他們管理很多對(duì)象的狀態(tài),不過(guò)不像ORM模型,他們不代表一條單獨(dú)的記錄數(shù)據(jù)。它們與Backbone的集合也不相同。除了簡(jiǎn)單的管理ORM風(fēng)格的對(duì)象集合外,store管理應(yīng)用中特定域的應(yīng)用狀態(tài)。
比如,F(xiàn)acebook的視頻回放編輯器利用一個(gè)TimeStore管理回訪時(shí)間點(diǎn)和回放狀態(tài)。另一方面,應(yīng)用的ImageStore管理圖片集合。在我們TodoMVC示例中的TodoStore和他們差不多,它管理一個(gè)待辦項(xiàng)集合。一個(gè)store展現(xiàn)出的特性就是模型的集合和一個(gè)邏輯模塊單件。
如上所述,一個(gè)store通過(guò)dispatcher注冊(cè)自己,并提供一個(gè)回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)接收action作為參數(shù)。在store注冊(cè)的這個(gè)回調(diào)中,通過(guò)switch語(yǔ)法,判斷action的類型,用來(lái)區(qū)分action,來(lái)調(diào)用store的內(nèi)部方法。在store更新后,他們廣播一個(gè)事件,來(lái)生命他們的狀態(tài)發(fā)生了改變,致使views可以查詢到新的狀態(tài),然后更新自己。
Views和Controller-Views
React提供了視圖層所需的可組合且可自由重新繪制的視圖。在嵌套的視圖層次結(jié)構(gòu)頂部,一種特殊的視圖監(jiān)聽(tīng)器,監(jiān)聽(tīng)那些依賴的store所廣播的事件。我們稱其為controller-view,因?yàn)樗峁┝苏澈洗a,用來(lái)從stores獲取數(shù)據(jù),然后將這些數(shù)據(jù)向下傳給它的后代鏈。我們可能會(huì)有一個(gè)用來(lái)管理頁(yè)面中任何重要部分的controller-view。
當(dāng)它從store接收事件,它首先通過(guò)store公開的getter函數(shù)請(qǐng)求它需要的新數(shù)據(jù)。然后調(diào)用自身的setState()或forceUpdate()函數(shù),致使運(yùn)行自身的render()函數(shù)和它所有子孫的render函數(shù)。
我們一般將store的整個(gè)狀態(tài)放在一個(gè)單獨(dú)的對(duì)象中沿著view鏈向下傳遞,允許不同的子孫使用他們需要的數(shù)據(jù)。除了將類似controller的行為保留在層次結(jié)構(gòu)的頂部,我們還要盡可能的使我們的后代視圖盡可能的只是功能,將store的整個(gè)狀態(tài)放在一個(gè)單獨(dú)的對(duì)象中向下傳遞,也有助于減少我們需要管理的props的數(shù)量。
偶爾我們可能也需要在更深的層次結(jié)構(gòu)中添加額外的controller-views來(lái)保持組件的簡(jiǎn)單。這會(huì)幫助我們更好的封裝層次結(jié)構(gòu)中與特定數(shù)據(jù)域相關(guān)的部分。然而請(qǐng)注意,深層次的controller-views可能會(huì)由于引入了新的數(shù)據(jù)流入口,從而違背單一數(shù)據(jù)流的原則。在決定是否添加深度控制器視圖時(shí),需要在獲取更簡(jiǎn)單的組件和不同點(diǎn)流入層次結(jié)構(gòu)的多數(shù)據(jù)更新的復(fù)雜性進(jìn)行之間進(jìn)行平衡。這些多數(shù)據(jù)更新將導(dǎo)致奇怪的效果,由于React的渲染函數(shù)被不同的controller-views重復(fù)的更新調(diào)用,從而可能增加調(diào)試的復(fù)雜度。
Actions
dispatcher公開一個(gè)方法,讓我們觸發(fā)一個(gè)派送到stores,并且包含一個(gè)裝載的數(shù)據(jù),我們稱其為action。action的創(chuàng)建可以被包裝在一個(gè)語(yǔ)義helper函數(shù)中,將action發(fā)送到dispatcher。比如,在一個(gè)待辦列表應(yīng)用中,我們打算改變一個(gè)待辦項(xiàng)的文本。我們可以在我們的TodoActions模塊中創(chuàng)建一個(gè)action以及一個(gè)函數(shù)簽名為updateText(todoId, newText)的函數(shù)。整個(gè)函數(shù)可以在我們的視圖事件處理器中調(diào)用,這樣我們就可以在用戶交互的響應(yīng)中調(diào)用到它了。這個(gè)action創(chuàng)建函數(shù)也可以添加一個(gè)類型到action中,這樣當(dāng)store解析它時(shí),可以得到適當(dāng)?shù)奶幚怼T谖覀兊睦钭又?,這個(gè)類型可以命名為TODO_UPDATE_TEXT.
Actions也可能來(lái)自其他地方,比如服務(wù)器。比如,發(fā)生在數(shù)據(jù)初始化時(shí)。也可能放生在服務(wù)器返回一個(gè)錯(cuò)誤碼或服務(wù)器提供更新給應(yīng)用。
Dispatcher哪?
正如之前所提到的,dispatcher也可以管理stores間的依賴??梢杂肈ispatcher類的waitFor()函數(shù)來(lái)實(shí)現(xiàn)此功能。在TodoMVC application這么簡(jiǎn)單的應(yīng)用中,我們確實(shí)不需要使用這個(gè)方法,但在更為大型和復(fù)雜的應(yīng)用中,他非常的重要。
在TodoStore注冊(cè)的回調(diào)函數(shù)中,我們可以明確的等待依賴項(xiàng)先去更新,然后自己再往下執(zhí)行:
case 'TODO_CREATE':
Dispatcher.waitFor([
PrependedTextStore.dispatchToken,
YetAnotherStore.dispatchToken
]);
TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
break;
waitFor()接收一個(gè)單獨(dú)的參數(shù),該參數(shù)是dispatcher注冊(cè)索引數(shù)組,通常被稱作dispatch令牌。因此,調(diào)用waitFor()的store可以依賴另一個(gè)store的狀態(tài)來(lái)了解如何更新自己的狀態(tài)。
一個(gè)dispatch令牌是向Dispatcher注冊(cè)回調(diào)函數(shù)時(shí)由register()返回的:
PrependedTextStore.dispatchToken = Dispatcher.register(function (payload) {
// ...
});
更多關(guān)于waitFor(),actions,action創(chuàng)建器和dispatcher,請(qǐng)查閱Flux:Actions和Dispatcher.