React Hooks 是什麼?

2022年11月16日

💎 加入 E+ 成長計畫 與超過 300+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

雖然近幾年大部分開發 React 都是使用 Hooks,但 Hooks 是在 React 16.8 之後出現的,在面試時也會被問到 Hooks 的相關問題,本篇文章總結了

  • 什麼是 Hook?
  • Hook 解決了什麼問題?
  • Hook 的規則?
  • 常使用的 Hook?

什麼是 Hook?

在 React 16.8 之前,如果我們想要使用到生命週期的方法或狀態 (state),那我們只能使用 React 的 class component。但 React hooks 的出現,讓我們即使使用 functional component,也能夠使用到 React 的功能和狀態。在 React 中,用 use 開頭的函式我們會把它稱之為 Hook,有些 Hooks 是 React 內建的功能,例如:useStateuseEffect,但我們也可以創造自己的 Hooks。Hooks 相較於函式更嚴謹、有一些需要遵守的規範,我們接下來在下方會提到。

Hook 解決了什麼問題?

React 團隊開發 Hooks 主要為了解決這三個原因

  • 狀態相關的邏輯 (stateful logics) 在 class component 之間難以複用

    在使用 class component 時,React 並沒有提供可以將重複的邏輯添加到元件的方法。因此,開發者可能使用 render props 或是 higher-order components 的方式達到,但這個缺點會是需要重新架構元件,不僅麻煩、而且程式碼也不易讀 (React 文件戲稱為 wrapper hell)。因此, Hook 的解決的主要問題之一,正是讓共享狀態邏輯。

    使用 Hooks,開發者可以從元件中提取狀態相關的邏輯,並對它進行獨立複用。我們也不需要重組元件,就可以在元件中複用相同邏輯。舉例來說,如果某個應用程式需要偵測頁面滑動,並根據滑動來觸發某些函式,這時我們可以寫一個 useScroll,並在不同頁面共享這個 useScroll 的狀態邏輯。目前在 React 的社群中,有很多 Hooks 的函式庫,便提供了各式各樣可重複使用的 Hooks。

  • 在 Hooks 出現前,複雜元件的邏輯會越寫越讓人難理解

    當 class component 越來越龐大或邏輯複雜時,我們可能會在同一個生命週期方法內需要加入很多不相關的邏輯,例如:componentDidMount 要處理 data fetching 和事件偵聽器邏輯,不僅難以理解也不好維護,且在很多情況下,也無法把這些元件拆成更小的元件。

    為了解決此問題, Hooks 允許將一個元件拆為更小的函式,而不是根據生命週期的方法拆分,例如上段例子,data fetching 和 事件偵聽器兩種邏輯,可以在同一個元件,通過兩個 useEffect 各自處理,又或者兩者拆成兩個 custom hooks。透過這樣細分,不相關的邏輯可以拆到不同的 useEffect,讓副作用的邏輯更好管理。

  • class 對於開發者來說不好理解

    React 團隊發現,class 可能會是學習 React 的主要障礙。因為 class 的概念在 JavaScript 和其他語言中相當不同 (在 JavaScript 當中,class 是語法糖),如果開發者過去是寫其他語言,轉來寫 JavaScript 時,需要特別了解 class 在 JavaScript 是如何運作的。

    為了解決這些問題,Hooks 可以在沒有 class 的情況下使用更多 React 的特性。從概念上講,React 元件一直更接近函式。 Hooks 包含函式,但不犧牲 React 的實用精神,且不需要特別學習複雜的 functional or reactive programming。

Hook 的規則

  1. 只能在最頂層使用 Hook

    不能在 for 循環、if…else 或巢狀中(如: map )中使用 Hook,我們需要確保永遠都在 React 函式的最頂層以及任何 return 之前使用 (備註:在《為什麼只能在最頂端層呼叫 Hook?》一文當中,我們有詳細討論原因,這也是常考的面試題,推薦大家多讀讀)。

  2. 只能在 React 函式中使用 Hook

    無法在一般的 JavaScript 函式中使用 Hook,只有以下兩種情境可以使用

    • 在 React 的 functional component 中使用
    • 在自定義的 Hook (custom hook) 中使用其他 Hook

常使用的 Hook

  • useState:用於定義和保存元件中狀態 (state),會回傳一個包含兩個值的陣列,第一個值是現在 state 的值,第二個值是一個 setter function,我們可以透過這個 setter function 更新 state 值並觸發 re-render。
  • useEffect:當想執行副作用(side effect)時,會透過 useEffect 處理,例如:fetch api、紀錄追蹤、setInterval()。我們需要傳送兩個參數在 useEffect 中,第一是一個 setup function,代表我們想執行的副作用程式碼,如果最後我們要清除這個副作用,需要在最後回傳一個 cleanup function; 第二個參數會是一個陣列,陣列中的元素會是相依於這個副作用程式碼會用到的變數。
  • useLayoutEffect:與 useEffect 是類似的,唯一不同點在於執行時機不同,useLayoutEffect 會在 DOM 更新之後執行; useEffect 則是在 render 渲染結束後執行。
  • useReducer:也是一種管理 state 的 hook,可作為 useState 替代方案。接收兩個參數,第一個是 reducer,第二個為初始值。會返回兩個值,現在 state 的值和 dispatch   方法。當狀態管理邏輯變得更複雜時,通常會建議使用 useReducer 而非 useState
  • useCallback:在重新渲染之間,用來緩存函式的方法。會回傳一個被 memoized 的 callback 函式,只有在依賴發生變化時,才會更新。此方法通常用在性能優化時。
  • useMemo:在重新渲染之間,用來緩存計算結果的方法。傳入一個創建函式和依賴項目,創建函式會需要返回一個值,只有在依賴發生變化時,才會更新值。此方法通常用在性能優化時。
  • useRef:用來儲存記錄不需要渲染的值。會返回一個可變的 ref 物件,.current 屬性會被初始化為傳入的參數值(initialValue)。
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們