Redux 編程思想

導論

什么是 Redux

Redux 是 Flux 的一個變種, 是一種非常流行的單向數據流管理框架. 在 Flux 的基礎上, Redux 將數據流進行統一的管理, 讓數據流的變化, 變得可預測, 這也是 Redux 名字的由來.

我眼中的 Redux

Flux 早期的官方實現中, 并沒有 Reducer 的概念, Store 是一種服務的聚合體, 包括一些數據和操作, 而 Redux 中的 Store 已經是另一種概念了, 如果說 Flux 的早期實現是一個 OOP 的版本, 那 Redux 其實算是 Flux 的 FP 實現, 所以當我們將 OOP 的各種設計工具應用到基于 Redux 構建的應用中去時, 你會發現如此的格格不入, 一切都是離散的, 一個對象被變成了三部分, Action, Reducer, Store 中的一部分, 由于 OOP 在軟件開發領域的巨大影響, 為了能夠融入 OOP 社區做出了很多努力和嘗試, 其中典型的代表是基于 Duck-Module 的流派, 如阿里的 dva 等, 另外一些則屬于原生派, 通過各種 Util 來減少 Redux 的樣板代碼. 不過本文不討論這些實現, 本文將討論的是如何在 Redux 上實現這兩種風格的前端應用架構

基于 Redux 的 OOP 前端應用架構

OOP 的核心概念是類和對象, 我們開發類來封裝數據和操作, 提供對象來輸出它們, 通過組合和繼承來描述復雜的對象體系. 用各種工具來確保操作的邊界, 讓對象高內聚, 同時隔離彼此之間的風險.

所以如果沒有對象, 一切就都是徒勞的.

而 Redux 是基于 FP 的, 就像兩種不同的世界觀, 無論怎么擰巴, Reducer 也不可能成為 OOP 里的一員, Action 只是消息的傳遞者, 在 OOP 里我們通過給對象發送消息來實現方法的調用, 從這個角度看, Bind 了 Dispatch 的 ActionCreator 看起來更像是一個方法, 但是數據又都在 Store 里, 但仔細思考下, 結合以上這幾點, 我們大致上應該能理出一條通向 OOP 的道路

  • 隱藏 Reducer
  • 隱藏 Dispatch, ActionCreator 默認綁定 Dispatch
  • 將對象掛載到 Store 上, 隱藏 getState() API

有了思路, 那就開始搞吧

搞吧....其實本文只討論編程思想, 所以如何實現, 就得另起一篇了:-D

基于 Redux 的 FP 前端應用架構

討論完了 OOP, 然我們來看看 FP, FP 的核心不用說自然是函數了, 一切皆函數, 但要開發一個應用, 僅僅光靠函數這一個概念, 我們的工作量未免過于巨大, 因此需要引入一些有效的工具幫助我們, 比如 "管道" 和 "高階函數", 另外為了保持數據不可變, 我們還需要引入不可變的數據類型系統 "Immutable", 有了這些我們可以嘗試開始著手去搭建一個前端應用架構了.

如果說 OOP 是用一堆對象描述世界, 對象彼此通過消息來傳遞數據, 那在 FP 的角度來看, 世界就像一條條管道, 數據在管道中流動, 管道和管道之間可以并行, 也可以串聯, 甚至可以彼此包含. 在對象的世界里, 對象負責存儲和處理數據, 數據是動態的, 可變的, 而在管道的世界里, 數據是不可變的, 因為管道不負責存儲數據, 如果數據沒有任何消費方, 那數據本身就沒有意義了. 所以存儲數據是不必要的. 因為數據處理本身并不綁定數據, 這也使得數據處理的過程變得非常靈活. 總結下, FP 的前端應用架構基于以下四點

  • 用函數定義操作, 并解決數據并行的問題
  • 用高階函數來組合函數, 構建復雜的操作
  • 用管道解決數據的串行操作
  • 引入"Immutable" 保證數據不可變

基于這四點, 我們可以嘗試對前端應用架構使用 FP 的方式來進行抽象和描述, 比如對于一個包含界面的 Web應用, 從用戶輸入數據到傳遞給服務端的過程, 可以想象成一條管道

用戶輸入 -> UI響應并處理 -> 數據層響應并處理 -> 發送數據的管道響應并處理 -> 數據源響應并處理 -> 持久化
//反過來
用戶請求 -> UI響應并處理 -> 數據層響應并處理 -> 發送數據的管道請求并處理 -> 數據源響應并發送

在過去要保證這樣的串行操作, 可能存在諸多問題, 比如異步的處理, 所幸 ES7的 async/await 給我們帶來了便利, 異步和同步編程模型的統一使得降低了前端架構的復雜性. 整個前端應用架構看上去就像兩條流水線, 組合在一起就是一個環, 我叫它"環形架構"或者"流水線架構"

以其中的數據管道為例, 我們可以想象請求數據的引擎就是一個并行節點, 因為對不同的數據源, 請求引擎可能不同, 但是我們會統一成一個概念, 比如 fetch, 在 node 和微信小程序下就是完全不同的, 但是通過概念統一, 我們可以利用高階函數組合出一個能夠并行處理多種數據源的引擎, 利用管道對傳遞的參數和數據源響應的數據分別處理.

事實上, 前端架構中的很多部分都無法用對象描述, 包括層次, 垂直的和縱向的, 流程圖, 狀態圖, 時序圖等等. 但這些東西卻可以用數據管道來統一描述, 因為前端架構圖都是平面圖, 當我們描述前端架構的時候, 都是在同一平面上描述數據是如何流動和處理的, 無論我們如何劃分, 數據在某一平面上一定是某一種串行或者并行操作.

當你習慣這種思維模式, 我想你會上癮, 因為一切都是一個環上的某個操作, 無論多復雜的系統, 都可以被分解成一個簡單的操作, 粒度的控制完全取決于你.

然后我們把 Redux 放到這個應用的環中, 你會發現, Redux 代表的并不是整個環, 而是其中的一部分, 更像是一個DB, Store 提供 Dispatch 和 來觸發數據的寫入, Action.Type 如果用來表示數據類型顯然比用來描述操作更合理, 這樣 Action 本身就是可枚舉的了, 相對應的 Reducer 也變得可枚舉了, 然后整個前端應用的架構應該是

UI 數據管道 -> Redux 管道 -> 數據源處理管道 -> 數據源

我們將 UI 上的 local 數據放在 UI 數據管道處理, 同時 Redux 管道負責接管外部數據源的讀寫, 而數據源處理管道則負責通信中前后的數據處理, 如果應用很龐大, 你可以加入更多的平面, 去切分整個系統, 例如加入數據模型的重組, 比如使用 Reselect 庫重組數據模型

UI 數據管道 -> 數據模型重組管道 -> Redux 管道 -> 數據源處理管道 -> 數據源

只要你愿意, 你可以合理的切分更多的平面出來, 當然過多的管道會帶來通信和數據校驗的成本, 這些都是需要在架構設計中被考慮到的部分.

但當你在你的應用中使用這種架構基于 Redux 去開發, 你的應用應該會非常容易測試和維護, 但是在開發模式上, 采用 TDD 應該會更合適, 因為確保每個管道的可用性, 是將他們拼起來的唯一前提.

某野生前端架構師

寫于2017年夏末

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容