【譯】Flux入門

原文地址:blog.andrewray.me/flux-for-st… ,做者:Andrew Rayjavascript

TL;DR 當我在努力學習Flux時,我但願有人告訴我:它並不簡單,也沒有好的文檔能夠查,而且有許多靈活組件。html

我須要使用Flux嗎?

若是你的應用程序須要處理動態數據(dynamic data)的話,那麼答案就是yes,你可能須要使用Flux。前端

若是你的應用程序僅僅是無需共享狀態靜態視圖(static view),而且你從不保存也不更新數據,那麼你不須要使用Flux,Flux不會給你帶來任何好處。java

爲何是Flux?

皮一下,由於Flux是個適度複雜的主意,爲啥要增長複雜度呢?react

90%的iOS應用程序是表格視圖中的數據。iOS工具包具備良好定義的視圖和數據模型可讓應用開發變得簡單。git

可是在前端(Font End:HTML,JS,CSS),咱們甚至都沒有。相反,咱們遇到一個很大的問題:沒有人知道應該如何去構建一個前端應用。我從事這個行業多年,歷來沒人教給我「最佳實踐」,相反,他們教了我好多「庫(libraries)」,諸如jQuery,Angular,Backbone等等。可是真正的問題、數據流,仍然避開了咱們。github

什麼是Flux?

Flux是一個用來描述具備很是特定事件和監聽的「單向」數據流的詞。沒有官方的Flux庫,可是你須要Flux Dispatcher和任何的JavaScript event library算法

官方文檔寫的就像某人的意識流同樣,從這裏開始學習是不太好的。可是一旦你掌握了Flux,它能夠幫助你填補空白。架構

不要試圖把Flux同MVC架構進行比較,它們的類似之處只會使人困惑。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的效率也是很是高的。

相關文章
相關標籤/搜索