Well in Time
從 Flux 與 MVC 的曖昧關係來介紹 Flux
04-27-20166 Min Read

"要學Flux還是直接學Redux好呢?" "Flux我知道, 3D printer麻!"

今天這篇主要是想藉由 Flux 的介紹,進而探討 FluxMVC 的差異。會想記錄這篇的原因是前幾天跟我的師父(以前實習公司的 CTO)詢問我類似的問題,我才發現自己還沒有真正全盤瞭解過,只是很直接的去了解 Flux,並且使用,但是到底 Flux 做對了什麼,而 MVC 又犯了什麼錯呢?

首先我們回顧一下2014 F8大會 - Hacker Way的影片 2014 F8大會 - Hacker Way 其中 Jing Chen 用了兩張圖來做對比,說明 MVC 的觀念在複雜的 Application 下難以維護。

facebook MVC

經驗豐富一點的 Developer 看到這張圖的第一個念頭應該會是:"What the fxxx... 誰家的 MVC長這樣啊?" 沒錯!Jing Chen 事後在 Reddit 上也有回覆,主要是想利用這張圖把 MVC 在大型架構下,資料 與 視圖 之間的 bi-directional data flow,容易造成 cascading effects 的問題凸顯出來。

為了解決她說的這個問題,Facebook 提出了 Flux

Flux

Flux是一種讓你很容易做到 one-way data flow 的概念,讓你View中的每個 component 的 state 都能夠 predictable

facebook Flux

Views Dispatch Actions

Dispatcher 是一個重要的 event system,用來 broadcast events 以及 registers callbacks,一般來說 Dispatcher 是唯一且 global 的,可以參考 Facebook 的Dispatcher Library(題外話,有許多一些 Flux 的 framework 並非這樣使用)

簡單來看個 Dispatcher 的例子:

// 假設你initiate一個dispatcher
var AppDispatcher = new Dispatcher();
//.
//..
//...
//在你的 component.jsx 中,可能會有這樣的程式
createNewItem: function( evt ) {
  AppDispatcher.dispatch({
    actionName: 'newPhoto',
    newItem: { name: 'Happy Holiday' } // example data
  });
}

render: function() {
  return (<button onClick={ this.createNewPhoto }>New Photo</button>);
}

當每次的 onClick 發生後,View 就會透過 Dispatcher dispatch 出一個 Action,該 Action 可以包含一個 payload,說明你想做什麼事情以及你需要操作什麼資料

Store Responds to Dispatched Actions

Store 在 Flux的架構內,通常是 Singleton (一樣,有些 framework 並非這樣做,尤其是想達成 isomorphic 時,可以參考Yahoo Fluxible)

在 Flux 的概念中,Store 基本上是你唯一可以操作資料儲存資料的地方。去除操作資料的部分,聽起來有點像 MVC 中的 Model ? 更明確一點來說,Store contains Models

舉例來說,當你需要存放一些照片以及其 Meta data 時,你會 Create 一個 PhotoStore 來存放 Photo model 與 Meta model。你會依照資料的Domain 來切割你的 Store。

var PhotoStore = {
  // collection of model data
  photos: []
}

AppDispatcher.register(function(payload) {
  switch( payload.actionName ) {
    case 'newPhoto':
      PhotoStore.photos.push(payload.newPhoto);
      break;
  }
})

Store 會向 Dispatcher 註冊 Callback,依照各種 action 的類別執行相對應的資料操作。

Store Emits "Change" Event to View

當你的 Store 資料做完更新後,要告訴前端頁面去刷新視圖,通常可以在 Store 註冊的 Callback 中執行以下動作:

AppDispatcher.register(function(payload) {
  switch( payload.actionName ) {
    case 'newPhoto':
      PhotoStore.photos.push(payload.newPhoto);
      // trigger "Change" event 通知View去做更新
      PhotoStore.trigger('change');
      break;
  }
})

接著,如果你是搭配 React 當作你的 View 的話,可能會在 componentDidMount 時,binding 一個 Store listener

componentDidMount: function() {  
    PhotoStore.bind( 'change', this.photoChange );
},

在 listener 中重新 fetch store 資料,並且 setState 來 re-render Component

photoChange: function() {  
    var newPhotoData = PhotoStore.getPhoto();
    this.setState({
      photos: newPhotoData
    });
}

你的 Component 的 render function 大概會像這樣:

render: function() {
  var photosComponet = this.state.photos.map(function(photo, i){
    return (
      <li key={'photo'+i}>
        {photo}
      </li>
    );					
  });
  return (
    <div>
      <ul>
        {photosComponet}
      </ul>
    </div>
  );
}

看完簡單的 Flux 介紹後,讓我們再複習一次 Flux 的流程圖

facebook Flux

相信在業界打滾多年的 Developer 們應該早有疑惑了,Flux 的那張圖,跟最原始的 MVC 圖不是很像嗎?!

MVC definition in wikipedia

User 操作 View 所產生的任何 event,都會經由 Controller 來修改與更動相關的 Model,而 Model 再告知 View 是否需要做更動,聽起來也是蠻 one-way direction 的呀。

事實上,MVC 跟 Flux 都只是一個概念,因此有各種不同的實作,加上 MVC 在資料流的處理上,並不像 Flux 一般有較為明確的定義,多數時候 Model 的更動與 View 的刷新可能會透過 Controller 來管理,讓 Model 單純存放 data。

如此一來,假若今天 View 的操作更動了 Model,而 Model 的變化又刷新了 View,在系統龐大的時候,一來一往,就會讓你的資料與頁面狀態變得非常複雜,要追蹤某個頁面的變動到底是誰觸發的,或是哪個資料改變了,你必須從 Controller 去慢慢 trace。而若是遵照 Flux 的流程,任何 View 的 update 都只要去追蹤其 State 的來源 Store 即可,有一個明確的 flow 可以遵循,並且每個 View 所需要監聽的資料來源,可以依照 Store 來區分,這之間的資料流不會互相干擾。 另外一個 Flux 的好處是,能夠更輕鬆的做出更 Unit 的 Unit test。這是你在複雜的 Controller 中難以達成的。

當然,你可能會想:“這是你 MVC 用得不好“。

我覺得這樣講也沒什麼不對,如果你 MVC 用得很熟很順手,的確單單是 Flux 這個東西對你的誘因可能不高,但當一間明星公司大力 Promote,對於基本概念的定義又夠清楚單純時,還是值得你試試。更別說 Flux 搭配上 React 的宣告式寫法,用起來更是如魚得水。

結論

Flux 做的是:

  1. 改善資料狀態視圖狀態的 Data Flow
  2. 讓頁面的狀態 Predictable
  3. 資料流不會互相污染
  4. 讓你的測試更加容易

而 MVC 在關注點分離上的貢獻不可小覷,重視在將資料(Model)、視圖(View)、邏輯(Controller)拆開,各自負責各自的工作。

因此並非是 MVC 不好、不對,所以我們應該採用 Flux;Flux 是在 MVC 建立的基礎下,定義出一個清楚的 one-way direction 資料流,並且透過 ActionDispatcherStore 來幫助整個概念的實現。

One more thing

如同前面所提,Flux 的實作有很多種,這邊介紹的只是最基本的流程,很多 Framework 在設計自己的 Dispatcher, Action 與 Store 時,會有不同的方式,或許可以從這邊的比較下去看看

參考資料

© by Arvin Huang. All rights reserved.
theme inspired by @mhadaily
Last build: 05-05-2023