jQuery ♥️ Redux - Adapting old school jQuery with Redux


本篇文章 slide 請見此

In The Beginning

相信有很多開發者第一次接觸 Redux 就是從 React 了解過來,或是從 Redux 當作進入點,開始學習 React,如此一來的結果會是很容易忽略了 Redux 官方文件上一開頭就寫的 You can use Redux together with React, or with any other view library.。而基於之前得到的任務要求:「你要如何向只會 React,但不知道 Redux,甚至只會 jQuery 的開發者介紹 Redux?」的原因,所以這次從 jQuery + Redux 這有趣的組合出發,來重新檢視 Redux 是什麼。

Spaghetti Code


國外很有趣的飛行義大利麵怪物(Flying Spaghetti Monster)

Spaghetti code is a pejorative phrase for source code that has a complex and tangled control structure, especially one using many GOTO statements, exceptions, threads, or other "unstructured" branching constructs. It is named such because program flow is conceptually like a bowl of spaghetti, i.e. twisted and tangled.

對於 jQuery,一直以來我對他的印象就是跟義大利麵一樣裹成一團(相信應該也很多人跟我有一樣的印象),而利用 Redux 來將麵團解開就是這篇文章的最終目標。

Start from simple jQuery implementation

使用 GitHub API 搭配自己的 Bookmark API 來實作簡易的 Bookshelf 功能。

Fetch 資料並且 Update UI

從 GitHub API 取得 repo 列表時,同時從 local server 拿到 Bookmark 列表,並且將原本不存在的 is_saved 屬性加回於 repo list。

Toggle Bookmark 而且如果 request failed 恢復狀態

Toggle bookmark icon 之後會送出請求,新增/刪除 存在於 local server 的 Bookmark,當 request failed 將狀態改回去並且顯示 message。

Template based GitHub bookshelf implemented by jQuery - release 1.0.0

Not too bad, huh?

但,有很多地方藏著可怕的潛在問題。像是:重複的程式碼、操作分散、意料外的修改所造成的邏輯 Bug
修改 is_save 屬性的地方幾乎一樣但在不一樣 callback 做了兩次,page 要是在別的地方被改到,整個 fetching process 就會噴掉而且不知道為什麼。

Unit Test

基本上不可能做測試,如果正在看這篇文章的你覺得可以的話,還請教學相長一下🙏

IT'S TIME TO REDUX

What's Redux?

Redux is a predictable state container for JavaScript apps. It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. (From redux official site.)

那 Redux 與 MVC 的差異在哪裡?

傳統 MVC 在 Controller 內處理 Model 再把資料交給 View ,但當 Model 一多 MVC 的複雜度就會急劇增加,而 Redux 維持著單一個 State 來源試圖減少因為複雜度所造成的問題。
F8 2014 議程影片 (順帶一提,Redux 是由 Flux 所派生出來的)

當然還有其他 Pattern 像是 MVVM、MVP、VIPER 等等不錯的解決方案...而且老實說我從一開始接觸 Redux 的時候對於 Holding State 這件事相當反感,因為當時我最熟悉的 MVVM 就是透過 ViewModel <=> View 的 Data Binding 來消除 State,減少 Side Effect,但實際上 Redux Flow 相較於 Two Way Data Binding 所帶來的不可預期性,Redux 更為簡單並好測試。

REDUX FLOW

Getting Started with React, Redux and Immutable: a Test-Driven Tutorial (Part 1)

Redux 維持 State,並且只有單一方向的透過 dispatch actions 去更新 State,如同官方文件上所提到的 Redux is a predictable state container for JavaScript apps.,它非常的好理解與預期,甚至因為這一點,有很棒的開發工具能協助我們有效的的提升效率。
Redux Devtool 能檢視現在的 state、dispatched action、甚至返回已經執行過的步驟

使用 Redux 重構 jQuery App

加入 lastAction reducer 後透過 subscribe store 來取得最後執行的 action,對應 action 觸發其他 action。

使用 Redux 重構之後,現在我們解決了重複程式碼與迴避無可預期狀態改變的情形。

但,截至目前為止,code 變多了,它依舊無法被測試。

SPLIT APP INTO MODULES, and wrap it up as ESModule for jest

將 App 拆分正依照功能區分的模組

並且修改 subscribe,把異步動作註冊為不影響 action 的 middleware,同時 middleware 為了徹底解耦合利用 Curry 的方式做 DI。

最後留下的進入點就變得非常乾淨

並且可以依照模組進行測試,大功告成!

Bonus: redux-thunk

使用自己註冊一個 middleware 實作的方式是一個方法,但到底還是不夠精簡,在這個 Bonus section 裡面我另外又實作了一個使用 redux-thunk 時做了相同的功能,各位可以在 GitHub - release 3.0.0 找到 code,在這裡就不增加篇幅解釋了。

另外在 redux community 裡面處理 asynchronous action 的優秀 middleware 很多,關於其他 middleware,我整理了另一篇文章,希望可以幫助到各位做選擇 redux-observable/Redux-Saga

Wrap it up

這篇文章的專案裡面我們使用到了 JsRender 來做 template,所以實際上也可以再加上同系列的 JsViews + JsObservable 去實作 MVVM pattern,就兩者相對的比較來說,雖然以這個小專案來說 MVVM 實作程式碼肯定比較精簡,但redux/flux pattern 的 one way data flow 更好預期,不論是開發或是協作上都很有幫助。

另外就是 jq 本身並不像 React 一樣是 one way data flow,所以在當多人協作時難說會有人就打破了 one way data flow 造成管理上的混亂,所以既然都 redux 了,Why not React? Lol

最後希望各位可以找到最適合自己/團隊的開發方式/技術,來跟飛天義大利麵怪物 Say good bye! 祝各位平安喜樂、身體健康,我們下次再會。

References