跳到主要內容

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....

完形心理學!?讓我們了解“介面設計師”為什麼這樣設計

完形心理學!?讓我們了解“介面設計師”為什麼這樣設計 — 說服客戶與老闆、跟工程師溝通、強化設計概念的有感心理學 — 情況 1 : 為何要留那麼多空白? 害我還要滾動滑鼠(掀桌) 情況 2 : 為什麼不能直接用一頁展現? 把客戶的需求塞滿不就完工啦! (無言) 情況 3: 這種設計好像不錯,但是為什麼要這樣做? (直覺大神告訴我這樣設計,但我說不出來為什麼..) 雖然世界上有許多 GUI 已經走得又長又遠又厲害,但別以為這種古代人對話不會出現,一直以來我們只是習慣這些 GUI 被如此呈現,但為何要這樣設計我們卻不一定知道。 由於 完形心理學 歸納出人類大腦認知之普遍性的規則,因此無論是不是 UI/UX 設計師都很適合閱讀本篇文章。但還是想特別強調,若任職於傳統科技公司,需要對上說服老闆,需要平行說服(資深)工程師,那請把它收進最愛;而習慣套用設計好的 UI 套件,但不知道為何這樣設計的 IT 工程師,也可以透過本文來強化自己的產品說服力。 那就開始吧~(擊掌) 完形心理學,又稱作格式塔(Gestalt)心理學,於二十世紀初由德國心理學家提出 — 用以說明人類大腦如何解釋肉眼所觀察到的事物,並轉化為我們所認知的物件。它可說是現代認知心理學的基礎,其貫徹的概念就是「整體大於個體的總合 “The whole is other than the sum of the parts.” —  Kurt Koffka」。 若深究完整的理論將會使本文變得非常的艱澀,因此筆者直接抽取個人認為與 UI 設計較為相關的 7 個原則(如下),並搭配實際案例做說明。有興趣了解全部理論的話可以另外 Google。 1. 相似性 (Similarity)  — 我們的大腦會把相似的事物看成一體 如果數個元素具有類似的尺寸、體積、顏色,使用者會自動為它們建立起關聯。這是因為我們的眼睛和大腦較容易將相似的事物組織在一起。如下圖所示,當一連串方塊和一連串的圓形並排時,我們會看成(a)一列方塊和兩列圓形(b)一排圓形和兩排三角形。 對應用到介面設計上,FB 每則文章下方的按鈕圖標(按讚 Like / 留言Comment / 分享 Share)雖然功能各不相同,但由於它們在視覺上顏色、大小、排列上的相似性,用戶會將它們視認為...