[譯]Flux入門

原文地址: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的效率也是非常高的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章