文章出處

話說當時做 APP 時,三月不知肉味,再次將眼光投放前端,有種天上一天,地下一年的感覺。

Flux 是一種思想

了解的最好方式當然是看Flux官方文檔了。React 中文站點也能找到對應的翻譯版本,但及時性可能無法保證。

Flux不算框架,它是一種編程思想,抑或是一種程序設計范式(Design Pattern),應用架構(Application Architecture),我更習慣稱它為一種思想,與前端組件化的編程思想 react 相輔相成。

It's more of a pattern rather than a formal framework,

既然只是一種范式,可以看作是程序編寫過程中的一種指導,具體的實現就因人而異了。

現在世面上各種牌子的 Flux 實現都有,選擇的空間很大,Which Flux implementation should I use?這個 issue 里倒是列出了一些并附上了 npm 的下載量。

我們注意到 Redux 在其中是比較矚目的一個,之一。

雖然從最近更新的 Redux 文檔來看,

Can Redux be considered a Flux implementation?

Yes, and no.

--- Redux Doc 1.3-Prior Art

對于它是否是 Flux 的一種實現還有爭論,但了解 Flux 對于我們搞清楚時下前沿技術,保持技術人員的先進性,使用 Redux,也是有幫助的。但老實說,Flux 早在2014就出現在公眾視野了,到現在已算不得有多新。

Flux 與 React

React 將界面組件化,并實現了由數據驅動的層疊式更新。這過程中數據成為了程序中最為關鍵的一環。在傳統的 MVC 模式下,React 可以看作是 View 層面的東西,而其他方面,邏輯及狀態管理,則需要 React 之外的東西來接管,Flux 應運而生。

單向數據流,這些一核心理念可以和 React 很好地補充。當然也有另一層意思,Flux 不一定與 React 搭配才能用。用戶在 React 組件上進行交互,視圖將操作通過 Action 的形式由 Dispatcher 進行分發,各個Store 注冊并接收,處理自己所負責的 Action,然后將更新重新反映到視圖上。

整個流程下來,負責程序某一組件的 Store 只需要發送更新,而不用關心視圖怎樣根據狀態來變更。這種做法很符合 React 的聲明式編程風格(Declarative programming style)。

強勢插入科普環節

與聲明式編程相對應的是命令式編程(Imperative programming),具體來說,

  • 聲明式編程是告訴計算機你想要的結果,具體過程由計算機自動完成
  • 命令式編程則是事先告訴計算機操作步驟,你期待的輸出則可能會在程序執行完后出現

了解更多可以參見 StackOverflow 的這個提問Difference between declarative and imperative in React.js?

但是為什么說 React 是聲明式的編程風格呢?想想用 React 編程的時候,你通過掌控流程和狀態,告訴程序此刻應該是什么樣子,而根據當前狀態各視圖要怎樣切換,你無需多管,反正最終組合后的結果,就是你想要的樣子。

Flux 的組成

是時候祭出這張圖了。

來自 Flux 官方文檔里的示意圖

Flux 的理念里,包含三個重要組成部分。Actions,Dispatcher 和 Stores。如果有第四個的話,我想可能是 Controller Views。

各部分的關系可從上圖看出一二。用戶的操作觸發 Action,由統一的 Dispatcher 來分發,Store 接收到 Action 后各自進行處理,更新自己的數據和所負責的 View。

Actions

“我其實就一普通對象”,面對我們的鏡頭,Action 聳了聳肩。

是的,Action 就是一個普通的 JS 對象,里面包含了這次動作所攜帶的新數據。約定Action 對象里包含一個唯一標識該動作的字段(一般用常量表示),這樣在 Store 接收到該 Action 時可以用來判斷是否需要處理該 Action。

代碼來自官方 TODO 示例里面TodoActions.js https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/actions/TodoActions.js#L20-L25

...
/**
 * @param  {string} text
 */
create: function(text) {
  AppDispatcher.dispatch({
    actionType: TodoConstants.TODO_CREATE,
    text: text
  });
},
...

可以說,Actions 連接了視圖與 Dispathcer,負責發起一個動作來觸發數據更新。這個動作可以是來自界面用戶的操作,也可以是異步請求的返回。

Action 只是個普通對象,只有將它發送到 Dispathcer 才會發光發熱。像上面代碼中,創建一個 ToDo Item 的create方法,便是一個封裝我們的 Action,將它發送到 Dispathcer 的方法。我們稱之為 Action creator。而 TodoActions.js 則是集合了很多 Action creator 的對象。這個 Action 會在用戶點擊「添加」按鈕時調用。

我們當然可以在點擊事件里直接調用 Dispather 來發送 Action,但當程序龐大的時候,這樣做不利于維護。View 層面不應該直接與 Dispather 打交道,它只需要完成分內的事情:響應交互,從 Store 獲取數據渲染自己。而且,這樣包裝的另一好處是代碼更加語義化,一看便知道是創建操作,將 Actoin 的創建融合在一起,也方便管理。

Dispatcher

“你一定學過計算機原理,所以你一定知道集成總線(bus)。” 面對我們的鏡頭,Dispather 侃侃而談,沒有 Action 的拘謹,“對,我就是那根總線。”

一個 Flux 程序中只有唯一一個 Dispatcher。 Store 會在它上面注冊回調,它將 Action 分發給所有注冊過回調的 Store。它充當中央調度員的角色,管理所有的 Action 分發。

按照官方的解釋,之所以 Actions 需要經過一個統一的 Dispather 進行派發,是因為大型項目下,可以方便地管理 Action 之間的依賴。實際使用中,一些操作依賴于另一個操作的完成。當我們的 Action 都經過一個地方處理后,可以很容易實現這樣的依賴,Dispather 則會提供waitFor方法供我們使用。

Dispatch 中的依賴管理示例

Store 在向 Dispatcher 注冊回調時,會得到一個返回值,這個值是該回調在 Dispatcher 中的索引值,能夠唯一標識該回調。

代碼來自 Flux 官方文檔

PrependedTextStore.dispatchToken = Dispatcher.register(function (payload) {
  // ...
});

拿到這個索引值,我們便可以在waitFor方法中指定需要等待的操作了。

case 'TODO_CREATE':
  Dispatcher.waitFor([
    PrependedTextStore.dispatchToken,
    YetAnotherStore.dispatchToken
  ]);

  TodoStore.create(PrependedTextStore.getText() + ' ' + action.text);
  break;

上面的示例中,會在TODO_CREATE的操作會在PrependedTextStoreYetAnotherStore執行完成后才開始執行。

Stores

Store 中包含了程序的狀態和邏輯。可以類比 MVC 模式中的 Model。但一如官方文檔所解釋的那樣,MVC 中的 Model 更多的是一個單獨ORM對象,是對現實世界個體的抽象,比如 Person,Cat。 而 Store 則是可以看成是多種對象的組合,每個對象又只取需要的部分,它負責的是程序中一個組件中狀態的管理,所以它里面的狀態數據是和所負責的程序區域相關的,而并不是以 ORM 對象為單位的。

如此說來我倒覺得有點像MVVM中的 View Model。

譬如聊天框,它可能包含聯系人列表中的數據,用于在輸入@的時候進行提示,也會包含富文本對象用于插入種類媒體信息,與此同時,它本身還有一個輸入值的模型。總之,這個InputStore里組裝了所需的狀態。

Store 向 Dispatcher 注冊回調,這個回調接收 Action 作為入參。前面說道,Dispatcher接收到 Action 后會分發給所有注冊過回調的 Store,所以在 Store 里,一般會有switch語句去與 Action 中的類型進行比較,以判斷是否是這個 Store 關心的 Action,Store 只對自己關心的 Action 作出反應,更新狀態。

Store 更新自己后,向外派發 change 事件,controller-views 監聽change 事件,從 store 獲取到新數據,然后派發給所有 View 上的子節點。

關于 Store, 另一個重要的點是:Store 接收 Action 后自己處理內部的邏輯并更新相應的狀態數據,一輪更新下來后所有 Store 各自井然有序,內部的狀態沒有對外暴露,改變的唯一方法就是通過 Action。

狀態只在各自的 store 里管理,程序各組件狀態的分離,可以達到高度解耦的目的。由單向數據流來驅動,一目了然。而雙向綁定及不夠細化的各狀態,會導致一處變動,很多地方跟著變動,而這些變化都由開發者自己維護,程序復雜后便不太好掌控全局了。

關于 Controller-Views

視圖很好理解,如果是用 React,視圖便是組件調用形成的樹上面的各個綠葉,它們是用戶看到的視圖。而Controller-Views,
可以結合 React 的Controlled components來理解。雖然是兩樣東西,但都可以理解為綁定了數據后被數據所控制,所以叫 'controller','controlled'。

嚴格來說,數據驅動的情況下,誰又不是被數據所綁定和控制的呢,這里 controll-views 更強調的是作為根結點,與數據源打交道的這么一個角色,這么一層視圖。它監聽來自 Store 的change事件,然后向 Store 獲取最新的數據,接下來把數據沿著組件樹向下派發,通知各個組件更新。這里就完全進入 React 了,組件內部通過調用 setSate()或者forceUpdate()來重新觸發render()方法,以達到視圖重新渲染的目的。

Flux 學習資料

Awesome 系列! Github 上的 awesome react 倉庫里面,flux 部分

順便說一句,如果你要找資料,哪都不用去,直接在 github 上找該技術的 awesome 系列準沒錯。這個系列的特點是,內容多而全,大都還免費,缺點是在紛繁的世界里需要自己去甄別好與壞,awesome 的不一定都 awesome,適合的才是對味的。

Happy coding :)

參考


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()