React Hooks 是什麼?
2022年11月16日
雖然近幾年大部分開發 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 內建的功能,例如:useState
、useEffect
,但我們也可以創造自己的 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 的規則
只能在最頂層使用 Hook
不能在
for
循環、if…else
或巢狀中(如:map
)中使用 Hook,我們需要確保永遠都在 React 函式的最頂層以及任何 return 之前使用 (備註:在 《為什麼只能在最頂端層呼叫 Hook?》 一文當中,我們有詳細討論原因,這也是常考的面試題,推薦大家多讀讀)。只能在 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)。