在前面的章節中,介紹了不少 React hooks,這章節將介紹另一個 React 核心觀念,那就是我們其實也可以客製化 hooks!
What are Custom Hooks ?
Custom Hooks 其實就是一般的 function,只是差別在於,它是一個 stateful 的 reusable function,而且可以在其中使用其他的 React hooks,不同於 component function 在使用 hooks 時,只能於最外層直接使用,無法使用於 scope 內,如 function、for…
Creating a Custom React Hook function
那麼在什麼時候會需要用到 custom hook 呢 ? 這個需求其實和一般開發時的思維一樣,凡是遇到相同需求的程式碼,都會盡可能的封裝它,使它可以被重複使用,也避免同一份程式碼反覆出現在不同的地方 ; 同理,custom hook 其實就是提供了一種方式,使我們可以封裝包含 React hook 的程式碼,使其在避免破壞 hook 使用規則下達到封裝的目的。
兩個非常類似的 component,一個是遞增的 counter,一個則是遞減,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const ForwardCounter = () => { const [counter, setCounter] = useState(0);
useEffect(() => { const interval = setInterval(() => { setCounter((prevCounter) => prevCounter + 1); }, 1000);
return () => clearInterval(interval); }, []);
return <Card>{counter}</Card>; };
const BackwardCounter = () => { const [counter, setCounter] = useState(0);
useEffect(() => { const interval = setInterval(() => { setCounter((prevCounter) => prevCounter - 1); }, 1000);
return () => clearInterval(interval); }, []);
return <Card>{counter}</Card>; };
|
而如果直接封裝 component 中的程式碼,就會違背 hook 的使用守則,那就是只能在 top level 中使用,因此需要將其封裝成 custom hook,其使用方式就是透過 use
開頭的 function,以便讓 React 知道它是一個 hook
1 2 3 4 5 6 7 8 9 10 11
| const useCounter = () => { const [counter, setCounter] = useState(0);
useEffect(() => { const interval = setInterval(() => { setCounter((prevCounter) => prevCounter - 1); }, 1000);
return () => clearInterval(interval); }, []); };
|
Using Custom Hook
有了 custom hook 之後,就可以透過它來取代原先的程式碼,和一般 hook 一樣,可以直接呼叫使用
1 2 3 4 5 6 7
| import useCounter from "../hooks/use-counter";
const ForwardCounter = () => { useCounter();
return <Card>{counter}</Card>; };
|
但很顯然的,如果只有做到這一步,就無法取得 custom hooks 其中的資料如 State,所以必須回傳期待在外部可以接收到的資料,回傳的格式可以是任何型別,如 object、array…,而這裡要特別注意的是,如果在多處使用同一個 custom hooks,彼此的資料是”不會共享”的,每次呼叫一次 custom hooks,就會重新產生一組新的資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const useCounter = () => { const [counter, setCounter] = useState(0);
useEffect(() => { const interval = setInterval(() => { setCounter((prevCounter) => prevCounter + 1); }, 1000);
return () => clearInterval(interval); }, []);
return counter; };
const ForwardCounter = () => { const counter = useCounter();
return <Card>{counter}</Card>; };
|
Configuring Custom Hooks
延續上方的範例,在 ForwardCounter & BackwardCounter 中其實有些微的差異,導致它們為一加一減 counter,custom hooks 其實就是一個 function,所以可以透過帶入參數來作為判斷的條件,上述的範例只要透過一個參數就可以判斷是否為遞增的 counter 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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]);
return counter; };
const ForwardCounter = () => { const counter = useCounter(true);
return <Card>{counter}</Card>; };
const BackCounter = () => { const counter = useCounter(false);
return <Card>{counter}</Card>; };
|
資料參考
React - The Complete Guide (Incl Hooks, React Router, Redux)