原文地址:https://blog.andrewray.me/flu... ,作者:Andrew Ray
TL;DR 當我在努力學習Flux時,我希望有人告訴我:它並不簡單,也沒有好的文檔可以查,並且有許多變化的部分。
我需要使用Flux嗎?
如果你的應用程序需要處理動態數據(dynamic data)的話,那麼答案就是yes,你可能需要使用Flux。
如果你的應用程序僅僅是無需共享狀態靜態視圖(static view),並且你從不保存也不更新數據,那麼你不需要使用Flux,Flux不會給你帶來任何好處。
爲什麼是Flux?
皮一下,因爲Flux是個適度複雜的主意,爲啥要增加複雜度呢?
90%的iOS應用程序是表格視圖中的數據。iOS工具包具有良好定義的視圖和數據模型可以讓應用開發變得簡單。
但是在前端(Font End:HTML,JS,CSS),我們甚至都沒有。相反,我們遇到一個很大的問題:沒有人知道應該如何去構建一個前端應用。我從事這個行業多年,從來沒人教給我“最佳實踐”,相反,他們教了我好多“庫(libraries)”,諸如jQuery,Angular,Backbone等等。但是真正的問題、數據流,仍然避開了我們。
什麼是Flux?
Flux是一個用來描述具有非常特定事件和監聽的“單向”數據流的詞。沒有官方的Flux庫,但是你需要Flux Dispatcher和任何的JavaScript event library。
官方文檔寫的就像某人的意識流一樣,從這裏開始學習是不太好的。但是一旦你掌握了Flux,它可以幫助你填補空白。
不要試圖把Flux同MVC架構進行比較,它們的相似之處只會令人困惑。
正式入坑!我將按順序解釋概念,並且一個一個地構建它們。
1.視圖的“Dispatch”和“Actions”
Dispatcher(調度員)本質上是一個加入了額外規則的事件系統。它來廣播事件並註冊回調。全局的dispatcher只有唯一的一個,你應該使用Facebook Dispatcher Library。實例化非常容易:
var AppDispatcher = new Dispatcher();
假設你的應用程序有一個“新建”按鈕來向列表添加項目。
<button onClick={ this.createNewItem }>New Item</button>
點擊會發生什麼?你的視圖會調度一個非常具體的“操作”,其中包含操作名稱和新項目數據:
createNewItem: function( evt ) {
AppDispatcher.dispatch({
actionName: 'new-item',
newItem: { name: 'Marco' } // example data
});
}
“action”是Facebook創造的另一個詞。它是一個JavaScript對象,用以描述我們想要做什麼事情,以及做這件事我們需要的數據。正如你所見到的,我們要做的事情就是添加一個new-item
,我們需要的數據就是項目name
。
2."Store"響應調度的操作
像Flux一樣,“Store”這個詞也是Facebook創造的.對於我們的應用程序,我們需要列表的特定邏輯和數據集合。這描述了我們的Store,我們稱之爲ListStore。
Store是一個單體對象,意味着你可能不能通過“new”關鍵字來聲明它,應用程序中每個Store裏只有一個實例。
// Single object representing list data and logic
var ListStore = {
// Actual collection of model data
items: []
};
然後,Store會響應已分派的操作:
var ListStore = …
// Tell the dispatcher we want to listen for *any*
// dispatched events
AppDispatcher.register( function( payload ) {
switch( payload.actionName ) {
// Do we know how to handle this action?
case 'new-item':
// We get to mutate data!
ListStore.items.push( payload.newItem );
break;
}
});
這是Flux處理調度回調的傳統方式。每個payload
包含一個action的名稱(actionName)和數據(newItem),switch語句確定Store是否應該響應action,並且知道根據action的類型處理數據變化。
🔑關鍵點:store不是數據模型,一個Store包含模型。🔑關鍵點:store在你的應用程序中唯一知道如何更新數據的東西,它是Flux中最重要的部分。我們調度的action並不知道如何添加或者刪除項目。
舉個栗子,假如應用程序中不同的部分需要保持跟蹤某些圖片及其元數據,那麼你就需要創建其他的store,並將其命名爲ImageStore。一個store相當於應用程序中一個單獨的“域(domain)”,如果應用程序非常龐大,這些域可能對你來說已經很明顯了。如果應用程序很小,你可能只需要一個store。一般來說,一種模型類型只對應一個Store。
只有store允許註冊Dispatcher的回調!view永遠不應該調用AppDispatcher.register
。Dispatcher應該只用於將消息從視圖View發送到Store。視圖(view)會響應不同類型的事件。
3. Store觸發“Change”事件
即將完成!現在數據確實已經變化了,我們需要告訴全世界!
Store觸發一個事件(Event),但是不會使用dispatcher。這雖然令人困惑,但是這就是Flux的方式。讓我們給我們的Store加入觸發事件的能力。如果你正在使用MicroEvent.js,那麼很簡單:
MicroEvent.mixin( ListStore );
然後,觸發changes事件
case 'new-item':
ListStore.items.push( payload.newItem );
// Tell the world we changed!
ListStore.trigger( 'change' );
break;
🔑關鍵點:當我們觸發事件的時候,我們不會傳遞最新的項目。視圖View只關心有事情發生變化了。讓我們繼續關注數據以瞭解原因。
4. 視圖(View)響應“Change”事件
現在我們需要展示列表。當列表發生變化時,視圖會完全地重新渲染(re-render)。
首先,當組件“安裝(mount)”時,即組件首次被創建的時候,從ListStore中監聽change事件:
componentDidMount: function() {
ListStore.bind( 'change', this.listChanged );
},
爲簡單起見,我們只調用forceUpdate,它可以觸發重新渲染(re-render)
listChanged: function() {
// Since the list changed, trigger a new render.
this.forceUpdate();
},
當組件“卸載(unmount)”的時候,不要忘記清除事件監聽器
componentWillUnmount: function() {
ListStore.unbind( 'change', this.listChanged );
},
現在怎麼辦?讓我們來看看我的render函數,我故意將其保存到最後。
render: function() {
// Remember, ListStore is global!
// There's no need to pass it around
var items = ListStore.getAll();
// Build list items markup by looping
// over the entire list
var itemHtml = items.map( function( listItem ) {
// "key" is important, should be a unique
// identifier for each list item
return <li key={ listItem.id }>
{ listItem.name }
</li>;
});
return <div>
<ul>
{ itemHtml }
</ul>
<button onClick={ this.createNewItem }>New Item</button>
</div>;
}
現在已經完整了。當你添加新項目的時,View 發出用戶的 Action,Dispatcher 收到 Action,要求 Store 進行相應的更新,store改變數據,然後store會觸發change事件,最後視圖通過重新渲染頁面來響應change事件。
譯者注:原文無此圖
但這裏有一個問題:每次列表更改時我們都會重新渲染整個視圖!這不是非常低效嗎?
不。
當然,我們將再次調用render函數,並確保渲染函數中的所有代碼都將重新運行。但是,如果渲染輸出已更改,React將僅更新真實DOM。您的render函數實際上是生成一個“虛擬DOM”,React與之前的輸出進行比較render。如果兩個虛擬DOM不同,React將僅使用差異更新真實DOM.
🔑關鍵點:當Store的數據改變時,視圖不應該關心是否有東西被添加,刪除,或是被改變了。視圖應該只去做重新渲染。React 的“虛擬DOM”差異算法會去處理這些重大問題,找出那些真正發生變化的DOM節點。這會讓您的生活更加簡單,並降低您的血壓。
還有一件事:“Action Creator”到底是個啥?
記住,當我們點擊按鈕的時候,會分配一個具體的動作(action):
AppDispatcher.dispatch({
eventName: 'new-item',
newItem: { name: 'Samantha' }
});
好吧,如果您的許多視圖需要發送此操作,則可以重複輸入。此外,您的所有視圖都需要知道特定的對象格式。那太蹩腳了。Flux建議一種抽象,稱爲Action Creator
,它只是將上述內容抽象爲一個函數。
ListActions = {
add: function( item ) {
AppDispatcher.dispatch({
eventName: 'new-item',
newItem: item
});
}
};
現在您的視圖可以調用ListActions.add({ name: '...' });
,而不必擔心調度的對象語法。
PS:不要使用forceUpdate
我因爲習慣了forceUpdate
這個簡單的緣故。組件讀取store數據的正確方法,是將數據拷貝到state
,並且在render
函數中讀取this.state
。您可以在TodoMVC example中看到它的工作原理。
首次加載組件時,store的數據被拷貝到state
中,當store更新時候,數據被完整地重新拷貝。這樣做是更好的,因爲在內部,forceUpdate
是同步的,同時setState
的效率也是非常高的。