React Code Reuse - Custom Hooks
本文介紹為什麼我們要建立並使用 Custom Hooks,以及講解如何撰寫創建自己的 Hooks,讓我們在開發 React 專案時更好地複用各種邏輯與程式碼。
什麼是 Custom Hook
- Custom Hooks 可以使用 React Hooks 與 State 等函式
- 使用時機:當不同元件裡有著一定程度的共通邏輯時,我們會想要複用邏輯,在各個元件中只去撰寫不同的部分
- 特性:每一次使用 Custom Hook 時,各別內部的 State 與 Effect 都是完全獨立的
示範建立一個 Custom Hook
Custom Hooks 其實不是 Functional Components,它是一個函式,只是在做法上有點類似
首先我通常習慣在 src
底下建立一個名為 hooks
的資料夾,專門用來存放自己建立的 Custom Hooks。而 Custom Hooks 的檔案名稱也是依照個人喜好即可,我自己喜歡用 useXxx.js
作為命名規則,舉例來說,新增 src/hooks/useCounter.js
檔案。
創建好檔案後,我們必須在將函式名稱命名為 useXxx
,例如:useCounter
。這種函式名稱的命名方式是必須遵守的規範,這是為了讓 React 能夠辨別這是一個 Custom Hook,讓你能夠在裡面使用 useEffect
等 Hooks。
1import { useEffect, useState } from 'react'; 2 3const useCounter = () => { 4 const [counter, setCounter] = useState(0); 5 6 useEffect(() => { 7 const interval = setInterval(() => { 8 setCounter((prevCounter) => prevCounter + 1); 9 }, 1000); 10 11 return () => clearInterval(interval); 12 }, []); 13 14 return counter; 15}; 16 17export default useCounter; 18
如同內建的 React Hooks,這個 Custom Hook 也會 return
東西,我們建立的 Custom Hook 可以回傳「任何」型別。在這個範例當中,我是回傳一個計算過後的 number
。
最後記得導出這個 Custom Hook,這樣我們才能在其他元件中調用它。
使用自己建立的 Custom Hook
現在我們就到其他元件中使用 useCounter
,以此取得回傳值(到這裡,我們就已經成功做到邏輯拆分囉)。
在使用 Custom Hooks 的時候,如果在「多個元件」中使用同一個 Custom Hook,每個元件都會產生一套自己的 Custom Hook,也就是裡面使用的 State 或 Effect 等資料都是「不會共用」的。
共用的是邏輯,不會共用狀態
1const ForwardCounter = () => { 2 const counter = useCounter(); // 這個 counter 是 Custom Hook 回傳的 3 4 return <Card>{counter}</Card>; 5}; 6 7export default ForwardCounter; 8
接下來,我們可以再針對不同的邏輯去做改變,像是透過「參數」來指定不同的邏輯。例如:透過 forwards
參數,給予 Custom Hook false
表示遞減,預設的 true
則為遞增。
1const useCounter = (forwards = true) => { 2 const [counter, setCounter] = useState(0); 3 4 useEffect(() => { 5 const interval = setInterval(() => { 6 // 根據參數的值判斷要執行的動作 7 if (forwards) { 8 setCounter((prevCounter) => prevCounter + 1); 9 } else { 10 setCounter((prevCounter) => prevCounter - 1); 11 } 12 }, 1000); 13 14 return () => clearInterval(interval); 15 }, [forwards]); // 記得把參數放入 Dependencies array 16 17 return counter; 18}; 19
使用上加上參數,例如:選擇傳入 false
表示要執行遞減。
1const BackwardCounter = () => { 2 const counter = useCounter(false); 3 4 return <Card>{counter}</Card>; 5}; 6
使用 Custom Hooks 的注意事項
1. 小心傳遞參考類型
如果 Custom Hook 回傳的內容包含函式(或物件),在使用 useEffect
時「不要」將它們加入 dependencies array 來偵測變化。
為什麼?理論上應該加入沒錯,但實際上這會導致無限循環!因為每次生成的新函式或物件雖然看起來一樣,但實際上是不同的引用,這會導致 useEffect
不斷重跑。
解決方法:對可能變動的函式(或物件)使用 useCallback
(或 useMemo
),這樣就能確保它們是同一個引用。
1import { useState, useEffect, useCallback } from 'react'; 2 3const useCustomHook = () => { 4 const [value, setValue] = useState(0); 5 6 const updateValue = useCallback(() => { 7 setValue((prev) => prev + 1); 8 }, []); 9 10 return { value, updateValue }; 11}; 12 13const MyComponent = () => { 14 const { value, updateValue } = useCustomHook(); 15 16 useEffect(() => { 17 updateValue(); // 加上 useCallback 避免導致無限循環 18 }, [updateValue]); 19 20 return <div>{value}</div>; 21}; 22
2. 避免在使用 Custom Hook 時傳入參數,將外部依賴改為函式參數
除了使用 useCallback
或 useMemo
之外,還有另一個解決方法,就是將 Custom Hook 所依賴的變數改為函式的參數。
這意味著在 Custom Hook 內部使用這些變數的地方,我們直接把它們作為函式參數來傳遞。這樣一來,我們就不需要在 Custom Hook 中傳遞這些變數,Custom Hook 也不需要新增 dependencies。
1import { useEffect } from 'react'; 2 3const useCustomHook = () => { 4 const logDependency = (dependency) => { 5 console.log(dependency); 6 }; 7 8 return logDependency; 9}; 10 11const MyComponent = ({ someProp }) => { 12 const logDependency = useCustomHook(); 13 14 useEffect(() => { 15 // 傳遞 someProp 作為參數給 logDependency,而不是直接依賴 someProp 16 // 減少了不必要的重複渲染和潛在的錯誤 17 logDependency(someProp); 18 }, [someProp, logDependency]); 19 20 return <div>{someProp}</div>; 21}; 22
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- Custom Hooks