跳到主要內容

npm uninstall redux

後hooks時代,我把redux從專案中移除了…
早先16.3 推出了Context Api 時,我就有寫過一篇文章:
說明 redux 的角色日漸式微,其實你的 react 專案不一定非得搭配 redux 不可,去年底 react team 宣布了 Hooks Api ,引進各種炫砲新工具,useReducer、useContext、useEffect,種種跡象顯示擺脫 redux 指日可待
「多想兩分鐘,其實你可以不用redux」,這兩天挑了一個小規模的專案重新改寫練練手,結果遠比想像中簡單呢…

改寫之前

先來個前情提要,我改寫的專案使用 redux 情境如下:
  • 專案使用 redux 但極少量使用來做 state management ,多數商業邏輯實踐在 Route level 的元件中
  • 搭配使用 redux-persist 來處理 data persistence,狀態儲存在 localStorage 或 sessionStorage 中
  • 搭配使用 redux-saga 來處理 ajax request
  • 搭配使用 redux-responsive 來處理 responsive design
如果要把 redux 從專案中抽掉,就必須要尋找上述需求的替代方案,我的作戰計畫如下:
  • 使用 useReducer 跟 useContext 來打造一個類似 redux 的輕量 State tree datastore,拿來存 App level 的共用資料
  • 自刻一個 persist enhancer 搭配上述的 reducer 使用
  • 在元件中直接使用 useState,useEffect 來處理 ajax request
  • 自刻一個 useBreakpoints 的 Hooks 來取代 redux-responsive
這邊先假設讀者對 Hooks 都有初步認識,所以接下來的內容會直指問題核心,如果對 Hooks 還不清楚的朋友要煩請先到官網把文件讀個幾遍喔

useReducer + useContext = redux

sort of…
撇開 middleware、enhancer 這些外掛不說,redux 不過是一個 State tree datastore,透過 react-redux 提供的 Provider 元件從最頂層包住整個 App,在需要連結的子(孫)元件使用 connect HOC ,state tree mutate的時候就可以透過 props 接到更新
這個部分 Context Api 完全可以取代
State tree mutation 則是透過 reducer 這樣的 pattern
function reducer (state, action) {
  // do something
  return newState
}
所謂的 reducer 簡單說,某個 action 透過 dispatch 發佈進到 reducer 裡,你拿 action payload 跟上一動的 state 攪和攪和以後產生一個新 state 然後回傳,這就是 reducer 的真面目
回頭看看 Hooks 官方文件的 useReducer ,我說這使用姿勢相似度87像
redux:抗議!他抄我。
useReducer:這就跟跑步游泳一樣,大家 flux一家親,沒有誰抄誰辣
所以技術上來說,要達成一個輕量化 redux ,使用 useContext + useReducer 是完全可行的,不過實作上有些眉眉角角要注意…
官網提供的 useReducer 範例只是單純使用單一 switch case reducer,而多數人使用 redux 習慣上是把 reducer 分拆成數個方便管理,再用 redux 提供的 combineReducers 合併成一個 rootReducer
我本來想沿用舊代碼,但發現這個 rootReducer 似乎跟 useReducer 接不太上,奇怪,記得明明是同樣的 signature ,有可能 redux 內部還有多做其他的事,後來轉念一想,最終目標是要丟掉 redux 相依,刻一個 combineReducers 也不難,就隨手寫了 helper
此外,早先寫 reducer 的時候我們有採用 reduxsauce 這樣的 helper,用 handlers map 比 switch case 的寫法漂亮多了,於是我又另外刻了createReducer 這樣的 helper,至於 createTypes / createActions 我嫌囉嗦,打算 dispatch 的時候直接 hard code { type: ‘ACTION_TYPE’ } ,反正我們的dispatch 的 action 不多,實在無需 Action Creator 這樣脫褲子放屁…
Create Reducer 的部分
另外 immutable 之類的工具也不用了,反正記得 reducer 回傳的 state 得是一個新的 object 不然是不會動的,有需要搭配使用的同學就自由發揮吧
這邊是合併 reducers 的部分
Data persist 的部分參考了原先使用的 redux-persist 介面寫了自家用解法,這些小工具我也不好意思獻醜拉,如果有需要上述 helpers (combineReducers/createReducers/persistReducer)的同學可在底下留言,看需要的人多的話我再來發 gist
我們來看看這在元件中如何應用,首先跟使用 redux-react 一樣,在最外部我們要用一個 Context.Provider 包住
然後在某個子元件如要使用的話,可以透過 Context.Consumer render props,不過既然我們都用了Hooks, 何不直接使用 useContext 更快,個人認為這樣的寫法比 Consumer render props 或 HOC 的解法都來的乾淨
整個 import 步驟有點多,其實可以把分散的 Code 整理整理放到同一個檔案方便外部引入
這邊我用了一個小伎倆,named export 是給 Provider,default export 則是給 Consumer,因為多半情境你會比較常 import 後者,前者只有在 app.js會需要設定一次
在 app.js 會變成這樣
元件裡引用則變這樣
如此一來是不是就更簡潔了呢? 好了我們這邊一口氣解決了兩個需求
2 down 2 to go :D

useBreakpoints

在講重頭戲 ajax request 之前先來個相較簡單的當作中場休息,這趴來說說如何自幹 Hooks 取代 redux-responsive(迷之音:我褲子都脫惹結果你給我跳話題…)
redux-responsive 使用上你可以設定 breakpoints,然後它在 redux state tree 內提供了一整組 boolean flags 讓你可以做查照,在元件裡就可以做類似下面的事
...
在 react-use 的專案中我看到了 useMedia 這樣的 Hooks,基於 Hooks 不過是函數,你可以利用原有的 Hooks 做 composition,於是借用了 useMedia 寫了如下的代碼
useMedia 這樣的 hooks 如果直接使用在各個元件中,每次使用都會訂閱 matchMedia onChange event,這樣一來會做太多不必要的訂閱,其實只要在 App level 訂閱一次,其結果再用 Context 傳遞到子元件即可,如此一來就能省去重複的event listener,於是在之上我又多加了 Context,設置方法就跟上面的 useAppReducer 差不多
在子元件中使用的方法如下
import useBreakpoints from 'Hooks/useBreakpoints'
function Foo () {
  const breakpoints = useBreakpoints() 
// breakpoints.lessThan.md is equivalent to browser.lessThan.medium
我覺得這是體現 Hooks 概念強大的一個例子,Composition + Reusability ,有了 Hooks 很多邏輯都能抽出來重複運用,如果說 Component 是視圖的樂高積木,那 Hooks 就是商業邏輯的樂高積木了。

什麼?可以在元件中直接呼叫 ajax request?這合法嗎?
其實打從用 react 以來我一直沒有細究說為什麼不能從元件中直接呼叫 ajax request,大家開始學的時候透過 redux 去串 thunk/saga 來處理 ajax request 彷彿就是理所當然的事。
我能想到的大致上是怕說這些非同步的 response callback 如果執行時元件已經不在了會造成潛在的 memory leak,我猜是這樣,歡迎留言分享正解,感謝大大無私分享,施主好人一生平安
不過現在我們有了 useEffect ,你可以大大方方在元件內處理 side effect 了,以下用一個 Sign In form 來做為探討案例
ajax request 的觸發是透過一個 pending 的 flag 來操作,我們可以用一個 if statement 確保在 componentDidMount 的階段不會發出 request ,只有當 pending 由 false -> true 的時候會觸發
接下來元件中不管是 button onClick 或是 form onSubmit,要做的事就很簡單,只是單純 setPending(true) 這樣,記得 request succeed/failed 要把 flag set 回 false
網路上有些案例是利用 setUrl 來做觸發,其實概念上大同小異,大家可自由發揮,不過建議是集中在 Route level 來處理這些 Ajax side effect,你如果要把層級拉到 App level,包一層 Network Component,然後搭配使用前面說的 useReducer/useContext 也是可行,那樣就更像以前的 redux 寫法

npm uninstall redux. Yes, you can.

記得前不久在 ReactJS.tw 社群,有人來叫板戰 vue vs react 優劣,結果裡面一半內容是在批評 redux 有多累贅難懂,我承認 redux 發展了這麼多年,生態系膨脹到有點讓人難以上手,但它的歷史定位是無庸置疑的
只是該走下神壇的總會走下神壇,react 生態系不是注定要跟 redux 鐵板一塊,之所以熱愛 react 最大原因就是看著它不斷進化,週邊工具可能殞落,但主角依舊屹立不搖
整個改寫過程下來並沒有太多的不適應,把 Class Component 改成 Function Component 再串上 Hooks 其實蠻簡單的,而且你也不用一次到位,有用到 Hooks 再改就好
這篇文章希望能幫助到還在觀望的人,我自己先當白老鼠把 redux 給拔了,效果拔擢,同事表示喜歡,我自己也覺得不賴…
所以說,你準備好 Hooks 了嗎?

留言

這個網誌中的熱門文章

2017通訊大賽「聯發科技物聯網開發競賽」決賽團隊29強出爐!作品都在11月24日頒獎典禮進行展示

2017通訊大賽「聯發科技物聯網開發競賽」決賽團隊29強出爐!作品都在11月24日頒獎典禮進行展示 LIS   發表於 2017年11月16日 10:31   收藏此文 2017通訊大賽「聯發科技物聯網開發競賽」決賽於11月4日在台北文創大樓舉行,共有29個隊伍進入決賽,角逐最後的大獎,並於11月24日進行頒獎,現場會有全部進入決賽團隊的展示攤位,總計約為100個,各種創意作品琳琅滿目,非常值得一看,這次錯過就要等一年。 「聯發科技物聯網開發競賽」決賽持續一整天,每個團隊都有15分鐘面對評審團做簡報與展示,並接受評審們的詢問。在所有團隊完成簡報與展示後,主辦單位便統計所有評審的分數,並由評審們進行審慎的討論,決定冠亞季軍及其他各獎項得主,結果將於11月24日的「2017通訊大賽頒獎典禮暨成果展」現場公佈並頒獎。 在「2017通訊大賽頒獎典禮暨成果展」現場,所有入圍決賽的團隊會設置攤位,總計約為100個,展示他們辛苦研發並實作的作品,無論是想觀摩別人的成品、了解物聯網應用有那些新的創意、尋找投資標的、尋找人才、尋求合作機會或是單純有興趣,都很適合花點時間到現場看看。 頒獎典禮暨成果展資訊如下: 日期:2017年11月24日(星期五) 地點:中油大樓國光廳(台北市信義區松仁路3號) 我要報名參加「2017通訊大賽頒獎典禮暨成果展」>>> 在參加「2017通訊大賽頒獎典禮暨成果展」之前,可以先在本文觀看各團隊的作品介紹。 決賽29強團隊如下: 長者安全救星 可隨意描繪或書寫之電子筆記系統 微觀天下 體適能訓練管理裝置 肌少症之行走速率檢測系統 Sugar Robot 賽亞人的飛機維修輔助器 iTemp你的溫度個人化管家 語音行動冰箱 MR模擬飛行 智慧防盜自行車 跨平台X-Y視覺馬達控制 Ironmet 菸消雲散 無人小艇 (Mini-USV) 救OK-緊急救援小幫手 穿戴式長照輔助系統 應用於教育之模組機器人教具 這味兒很台味 Aquarium Hub 發展遲緩兒童之擴增實境學習系統 蚊房四寶 車輛相控陣列聲納環境偵測系統 戶外團隊運動管理裝置 懷舊治療數位桌曆 SeeM智能眼罩 觸...
opencv4nodejs Asynchronous OpenCV 3.x Binding for node.js   122     2715     414   0   0 Author Contributors Repository https://github.com/justadudewhohacks/opencv4nodejs Wiki Page https://github.com/justadudewhohacks/opencv4nodejs/wiki Last Commit Mar. 8, 2019 Created Aug. 20, 2017 opencv4nodejs           By its nature, JavaScript lacks the performance to implement Computer Vision tasks efficiently. Therefore this package brings the performance of the native OpenCV library to your Node.js application. This project targets OpenCV 3 and provides an asynchronous as well as an synchronous API. The ultimate goal of this project is to provide a comprehensive collection of Node.js bindings to the API of OpenCV and the OpenCV-contrib modules. An overview of available bindings can be found in the  API Documentation . Furthermore, contribution is highly appreciated....
2019全台精選3+個燈會,週邊順遊景點懶人包 2019燈會要去哪裡看?全台精選3+個燈會介紹、週邊順遊景點整理給你。 東港小鎮燈區-鮪鮪到來。 2019-02-15 微笑台灣編輯室 全台灣 各縣市政府 1435 延伸閱讀 ►  元宵節不只看燈會!全台元宵祭典精選、順遊景點整理 [屏東]2019台灣燈會在屏東 2/9-3/3:屏東市 · 東港鎮 · 大鵬灣國家風景區 台灣燈會自1990年起開始辦理,至2019年邁入第30週年,也是首次在屏東舉辦,屏東縣政府與交通部觀光局導入創新、科技元素,融入在地特色文化設計,在東港大鵬灣國家風景區打造廣闊的海洋灣域燈區,東港鎮結合漁港及宗教文化的小鎮燈區,及屏東市綿延近5公里長的綵燈節河岸燈區,讓屏東成為璀璨的光之南國,迎向國際。 詳細介紹 ►  2019台灣燈會在屏東 第一次移師國境之南 大鵬灣燈區 主題樂園式燈會也是主燈所在區,區內分為農業海洋燈區、客家燈區、原住民燈區、綠能環保燈區、藝術燈區、宗教燈區、競賽花燈及317個社區關懷據點手作的萬歲光廊等。 客家燈籠隧道。 平日:周一~周四14:00-22:30(熄燈) 假日:周五~周六10:00-22:30(熄燈)  屏東燈區: 萬年溪畔 屏東綵燈節藍區-生態。 綵燈節--每日17:30 - 22:00(熄燈) 勝利星村--平日:14:00 - 22:30(熄燈) 假日:10:00 - 22:30(熄燈) 燈區以「彩虹」為主題,沿著蜿蜒市區的萬年溪打造近5公里長的光之流域,50組水上、音樂及互動科技等不同類型燈飾,呈現紅色熱情、橙色活力、黃色甜美、綠色雄偉、藍色壯闊、靛色神祕、紫色華麗等屏東風情。勝利星村另有懷舊風的燈飾,及屏東公園聖誕節燈飾。 東港小鎮燈區 東港小鎮燈區-鮪鮪到來。 小鎮燈區以海的屏東為主題,用漁港風情及宗教文化內涵規劃4個主題區,分別為張燈結綵趣、東津好風情、神遊幸福海、延平老街區。每日17:00~22:30(熄燈) 以上台灣燈會資料來源: 2019台灣燈會官網 、 i屏東~愛屏東 。 >> 順遊行程 小吃旅行-東港小鎮 東港小吃和東港人一樣,熱情澎湃...