儘量使用 useReducer,不要使用 useState

方應杭在飢人谷發表於2019-04-23

原文:useReducer, don't useState

本文難度:入門級別

本文預設你已經大概瞭解過 React Hooks,如果不瞭解可以先看看 ReactJS 的文件

當開發者們開始在他們的應用中使用 React Hooks API 時,很多人一開始都會把 useState 作為他們的狀態管理工具。 然而,我強烈認為 useReducer 比 useState 更適合做狀態管理。

首先我來定義一下『更適合』是什麼意思:

  • 更容易管理大量狀態
  • 更容易被其他開發者理解
  • 更容易被測試

接下來我分別對三點進行闡述。

管理大量狀態

這篇文章大部分觀點只是我的主觀看法。

useState 有一個與 class 元件裡面的 setState 明顯不同的地方,那就是 useState 不對狀態做淺層合併了,而 useReducer 會合並。

為了說明這一點,我這裡給一個使用 useReducer 來實現『撤銷/重做』的例子:

function init(initialState) {
  return {
    past: [],
    present: initialState,
    future: [],
  }
}
function reducer(state, action) {
  const { past, future, present } = state
  switch (action.type) {
    case 'UNDO':
      const previous = past[past.length - 1]
      const newPast = past.slice(0, past.length - 1)
      return {
        past: newPast,
        present: previous,
        future: [present, ...future],
      }
    case 'REDO':
      const next = future[0]
      const newFuture = future.slice(1)
      return {
        past: [...past, present],
        present: next,
        future: newFuture,
      }
    default:
      return state
  }
}
複製程式碼

用 useState 達到相同的效果有點困難,不過也並不是不可能。我只是想告訴裡使用 useReducer 是多麼地方便,這也引出了第二點。

譯註:如果用 useState 來做,只需要把 past / present / future 放到同一個 state 裡面即可,但是會造成程式碼分散。而且 useState 也不推薦你在一個 state 裡放太多東西,因為它不會合並 state,用起來不方便。

更容易被其他開發者理解

在 Web 開發中,我們面對的問題很多時候並不是純技術問題。大部分時候你都要跟其他開發者工作,他們的開發經驗很可能跟你的很不一樣。

由於大部分前端開發者都瞭解過 Redux,所以使用 useReducer 比使用 useState 更能快速獲得收益。其核心概念比如 diapatch 一個 action,使用 reducer 來更新 state,都比 useState 更容易被這些開發者掌握。

還有一點值得注意,那就是即使你目前是一個人在開發一個應用,你也保不齊以後會有其他人接手這份程式碼。

更容易被測試

要論 Hooks RFC、Twitter 裡被討論最多的話題,那就是如何測試 Hooks。我覺得要讓開發者理解測試 Hooks 的最佳實踐,還是要花費一些時間的(譯註:尤其是 useState)。但是如果你使用的是 useReducer,那麼你所有的跟 state 相關的業務邏輯程式碼都可以放到一個單獨的函式裡,跟你的元件分開,非常好測試。

把狀態更新程式碼和渲染邏輯分開,使得你可以把測試程式碼也分成這兩部分。以上面的 reducer 程式碼為例, 我們可以輕鬆地測試撤銷和重做,做法是把 mock 狀態和 action 傳給 reducer 即可,我們甚至不用引入 React!

test('it supports undoing the state', () => {
  const state = { past: [{ count: 0 }], present: { count: 1 }, future: [] }
  const newState = reducer(state, { type: 'UNDO' })
  expect(newState.present.count).toBe(0)
})
複製程式碼

總結

我並不期望大家只使用 useReducer 不使用 useState,我個人也不會這麼做,它們各有各的使用場景。但是我的確認為 useReducer 在複雜的狀態管理場景下比 useState 更好維護。

相關文章