"要學Flux還是直接學Redux好呢?" "Flux我知道, 3D printer麻!"
今天這篇主要是想藉由 Flux
的介紹,進而探討 Flux
與 MVC
的差異。會想記錄這篇的原因是前幾天跟我的師父(以前實習公司的 CTO)詢問我類似的問題,我才發現自己還沒有真正全盤瞭解過,只是很直接的去了解 Flux,並且使用,但是到底 Flux
做對了什麼,而 MVC
又犯了什麼錯呢?
首先我們回顧一下2014 F8大會 - Hacker Way的影片 其中 Jing Chen 用了兩張圖來做對比,說明 MVC 的觀念在複雜的 Application 下難以維護。
經驗豐富一點的 Developer 看到這張圖的第一個念頭應該會是:"What the fxxx... 誰家的 MVC長這樣啊?" 沒錯!Jing Chen 事後在 Reddit 上也有回覆,主要是想利用這張圖把 MVC 在大型架構下,資料 與 視圖 之間的 bi-directional data flow
,容易造成 cascading effects
的問題凸顯出來。
為了解決她說的這個問題,Facebook 提出了 Flux
Flux是一種讓你很容易做到 one-way data flow 的概念,讓你View中的每個 component 的 state 都能夠 predictable
。
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 在 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 資料做完更新後,要告訴前端頁面去刷新視圖,通常可以在 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 的流程圖
相信在業界打滾多年的 Developer 們應該早有疑惑了,Flux 的那張圖,跟最原始的 MVC 圖不是很像嗎?!
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 做的是:
資料狀態
與視圖狀態
的 Data FlowPredictable
而 MVC 在關注點分離上的貢獻不可小覷,重視在將資料(Model)、視圖(View)、邏輯(Controller)拆開,各自負責各自的工作。
因此並非是 MVC 不好、不對,所以我們應該採用 Flux;Flux 是在 MVC 建立的基礎下,定義出一個清楚的 one-way direction
資料流,並且透過 Action
、Dispatcher
與 Store
來幫助整個概念的實現。
如同前面所提,Flux 的實作有很多種,這邊介紹的只是最基本的流程,很多 Framework 在設計自己的 Dispatcher, Action 與 Store 時,會有不同的方式,或許可以從這邊的比較下去看看
參考資料