React Hooks | 既生 useState 何生 useReducer ?

一個使用 useReducer 比 useState 更好的個案

Leo Chiu
手寫筆記
7 min readMar 28, 2021

--

前言

React Hooks 提供了兩個管理狀態的 hook,分別是 useStateuseReducer,你在寫 component 應該經常會想「什麼時候該用 useState,又在什麼時候才用 useReducer」,「 useReducer 跟 redux 這麼像,幹嘛不用 redux 就好」,「沒事 React 團隊幹嘛抄一個這麼像 redux 的東西呢 😆」。

實際上有許多情況直接用 useState 會比要建一個 useReducer 更簡單,因為 useReducer 的使用方法是希望我們用像 redux 的方式來管理狀態,透過 action -> reducer -> store 的方式管理狀態流。你應該不會想在一個簡單的 component 寫一堆 reducer 的程式碼,不然後續維護時看到你可能會想殺掉以前的自己。

但是有些情況 useReducer 會比 useState 更好用,會寫出更容易維護的程式碼,接下來就讓我們來評估 useReduceruseState 兩者的使用時機吧!

👉 一個使用個案

有一個情境如下,現在你需要設計一個書籍清單列表,這個列表除了顯示各種書籍以外,還提供了以下幾個功能:

  • 使用者可以切換頁面,瀏覽書籍列表
  • 使用者可以自訂每一頁顯示幾本書籍資料
  • 使用者可以透過關鍵字搜尋書籍書名或分類
  • 使用者可以選擇書籍分類
  • 使用者可以用價格排序
書籍清單列表 UI

根據以上個需求,我們會設計一個物件來處理整件事情,這個物件大概會長的像下面這個樣子:

另外,我們希望這個網頁應用是 SPA (Single Page Application) ,亦即在切換頁面、輸入關鍵字或選擇書籍分類後,網頁不用重新整理就可以從伺服器拿到資料,現在有很多現成的套件可以幫我們做到這件事,像是 react-queryswr 等等。

但是我們沒有要在這個網頁中用到這些套件,只是有一個模擬的 useQuery 如下:

👉 useState 的作法

完整的 useState 版本實作 ⏳ Codesandbox

首先,我們先看到 useState 的作法,你大概會想要用幾個 state 來儲存這些參數,看起來很棒,各個狀態一目瞭然。

接下來你會開始撰寫各個事件被觸發時相對應的 callback function,每個 callback 各司其職,在 <Select /><Input /> 更新時幫我們把 state 設定回去。最後 useEffect 可以幫我們在關鍵字、分類、頁籤、排序等等的設定改變後把它們都組合起來,儲存在 variables 中。

useQuery 會在 variables 改變後,自動重新呼叫 API,跟伺服器請求新的資料。

如果你看完有些疑惑為什麼要這樣寫,像是 handleChangeKeywordhandleChangeCategory 之所以會需要重新設定pagination 是因為在輸入關鍵字或是改變書籍類別後,為了瀏覽搜尋結果必須回到第一頁,我們才要設定 current 為 1。

這樣看下來,整體看下來還行,那接著我們繼續來看看 useReducer 的版本。

👉 useReducer 的作法

完整的 useReducer 版本實作 ⏳ Codesandbox

現在我們把書籍列表中的邏輯用 useReducer 來改寫,把 variables 相對應的 callback function 分別都變成一種 action.type ,而回傳的值即是 API 所需要的 variables

然後再把原本 <App /> 裡面的 useState 替換掉,變成用 dispatch 方式修改 variables 的值。

你應該有發現,用 useReducer 修改過後的 component 精簡了許多,由於複雜的 state 邏輯都移動到了 reducer 裡面,我們在閱讀程式碼時就有跡可循,知道 callback 裡要做哪些事情,不用在閱讀程式碼時被各種複雜的 setState 交互弄亂自己的思考邏輯。

而且這篇文章中使用的網頁應用如果再大一些,需要控制的 fitler 數量很多,就要用很多的 useState 跟 callback 來處理複雜的 state 邏輯,為了後續維護,轉而用 useReducer 反而是更好的一種做法。

🎉 另一個 useReducer 的優點

有時候我們會需要把 callback 當作 props 傳遞到 child component 中,如此一來,為了避免 re-render 的問題同常會用 useCallback 記憶起來,但是代表說我們有非常多的 callback 需要綁在 useCallback 裡面,也許這不是件好事。

useReducer 的好處之一便是, dispatch 不會隨著 re-render 而重新分配記憶體位置,在作為 props 傳入到 child component 中時也可以不用擔心沒有 useMemo 而造成 re-render 的問題。

雖說 React 官網也有提到傳遞 callback 可以讓等體閱讀起來更明確,像是「管線」一樣,可以知道 child component 可能會做哪些事。但這是一些取捨,使用 dispatch 的方式會讓程式碼看起來更乾淨些,就看團隊是如何溝通了。

結論

這篇文章我們討論了一個實際上很常見的個案,而從這個個案中可以看到 useReducer 的程式碼比 useState 的程式碼更乾淨些。實際上的個案使用到的 filter 肯定會更多,在複雜的網頁應用中,可以見得 state 邏輯一定也會越來越複雜,在這樣的 component 中使用 useReducer 將 state 邏輯抽離出來,程式碼會變得更容易閱讀及維護。

題外話一下,這篇文章的標題是「 既生 useState 何生 useReducer 」,而且 React 的官方網站也提到 useReduceruseState 的一種替代性寫法,讓我們以為它們是不同的東西,但是誰知道 useState 的底層其實是用 useReducer 做的呢 😜。

有興趣請看 useState🔗原始碼

分享就到這邊,如果喜歡我的文章可以幫我拍個幾下手,在閱讀文章時如果有遇到什麼問題,或是有什麼建議,都歡迎留言告訴我,謝謝。 😃

--

--

Leo Chiu
手寫筆記

每天進步一點點,在終點遇見更好的自己。 Instragram 小帳:@leo.web.dev