【譯】Flux入門

司南free發表於2018-11-14

原文地址:blog.andrewray.me/flux-for-st… ,作者: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事件。

【譯】Flux入門
譯者注:原文無此圖

但這裡有一個問題:每次列表更改時我們都會重新渲染整個檢視!這不是非常低效嗎?

不。

當然,我們將再次呼叫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的效率也是非常高的。

相關文章