本文介紹為什麼我們要建立並使用 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。
import { useEffect, useState } from "react";
const useCounter = () => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter((prevCounter) => prevCounter + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return counter;
};
export default useCounter;
如同內建的 React Hooks,這個 Custom Hook 也會 return
東西,我們建立的 Custom Hook 可以回傳「任何」型別。在這個範例當中,我是回傳一個計算過後的 number
。
最後記得導出這個 Custom Hook,這樣我們才能在其他元件中調用它。
使用自己建立的 Custom Hook
現在我們就到其他元件中使用 useCounter
,以此取得回傳值(到這裡,我們就已經成功做到邏輯拆分囉)。
在使用 Custom Hooks 的時候,如果在「多個元件」中使用同一個 Custom Hook,每個元件都會產生一套自己的 Custom Hook,也就是裡面使用的 State 或 Effect 等資料都是「不會共用」的。
共用的是邏輯,不會共用狀態
const ForwardCounter = () => {
const counter = useCounter(); // 這個 counter 是 Custom Hook 回傳的
return <Card>{counter}</Card>;
};
export default ForwardCounter;
接下來,我們可以再針對不同的邏輯去做改變,像是透過「參數」來指定不同的邏輯。例如:透過 forwards
參數,給予 Custom Hook false
表示遞減,預設的 true
則為遞增。
const useCounter = (forwards = true) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// 根據參數的值判斷要執行的動作
if (forwards) {
setCounter((prevCounter) => prevCounter + 1);
} else {
setCounter((prevCounter) => prevCounter - 1);
}
}, 1000);
return () => clearInterval(interval);
}, [forwards]); // 記得把參數放入 Dependencies array
return counter;
};
使用上加上參數,例如:選擇傳入 false
表示要執行遞減。
const BackwardCounter = () => {
const counter = useCounter(false);
return <Card>{counter}</Card>;
};
使用 Custom Hooks 的注意事項
1. 小心傳遞參考類型
如果 Custom Hook 回傳的內容包含函式(或物件),在使用 useEffect
時「不要」將它們加入 dependencies array 來偵測變化。
為什麼?理論上應該加入沒錯,但實際上這會導致無限循環!因為每次生成的新函式或物件雖然看起來一樣,但實際上是不同的引用,這會導致 useEffect
不斷重跑。
解決方法:對可能變動的函式(或物件)使用 useCallback
(或 useMemo
),這樣就能確保它們是同一個引用。
import { useState, useEffect, useCallback } from "react";
const useCustomHook = () => {
const [value, setValue] = useState(0);
const updateValue = useCallback(() => {
setValue((prev) => prev + 1);
}, []);
return { value, updateValue };
};
const MyComponent = () => {
const { value, updateValue } = useCustomHook();
useEffect(() => {
updateValue(); // 加上 useCallback 避免導致無限循環
}, [updateValue]);
return <div>{value}</div>;
};
2. 避免在使用 Custom Hook 時傳入參數,將外部依賴改為函式參數
除了使用 useCallback
或 useMemo
之外,還有另一個解決方法,就是將 Custom Hook 所依賴的變數改為函式的參數。
這意味著在 Custom Hook 內部使用這些變數的地方,我們直接把它們作為函式參數來傳遞。這樣一來,我們就不需要在 Custom Hook 中傳遞這些變數,Custom Hook 也不需要新增 dependencies。
import { useEffect } from "react";
const useCustomHook = () => {
const logDependency = (dependency) => {
console.log(dependency);
};
return logDependency;
};
const MyComponent = ({ someProp }) => {
const logDependency = useCustomHook();
useEffect(() => {
// 傳遞 someProp 作為參數給 logDependency,而不是直接依賴 someProp
// 減少了不必要的重複渲染和潛在的錯誤
logDependency(someProp);
}, [someProp, logDependency]);
return <div>{someProp}</div>;
};
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- Custom Hooks