認識 Custom Hook
當有多個組件有相同的邏輯,但卻重複寫了好幾次,這時候可以將相同邏輯的地方,抽出來做成一個共用 function,方便我們使用。
Custom Hook 會自然遵循 Hook 設計的規範,且所有內部的 state 和 effect 都是完全獨立的。
Custom Hook 規則
必須以 use
做開頭,方便開發者一眼就知道這是可以使用的 Hook,Lint 工具也會自動檢查是否違反 Hook 規則,如:
1 2 3
| useDocumentTitle useCounter useInterval
|
Custom Hook 範例
以下會有兩個範例來做解說
範例一:useCounter
目前有兩個 Counter 的組件,分別是 Counter1.js、Counter2.js,裡面有增加、減少跟重置的按鈕
components/Counter1.js
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
| import { useState } from "react";
const Counter1 = () => { const [count, setCount] = useState(0);
const increment = () => { setCount((prevCount) => prevCount + 1); }; const decrement = () => { setCount((prevCount) => prevCount - 1); }; const reset = () => { setCount(0); };
return ( <div> <h2>Counter1:{count}</h2> <button onClick={increment}>increment</button> <button onClick={decrement}>decrement</button> <button onClick={reset}>reset</button> </div> ); };
export default Counter1;
|
components/Counter2.js
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
| import { useState } from "react";
const Counter2 = () => { const [count, setCount] = useState(0);
const increment = () => { setCount((prevCount) => prevCount + 1); }; const decrement = () => { setCount((prevCount) => prevCount - 1); }; const reset = () => { setCount(0); };
return ( <div> <h2>Counter2:{count}</h2> <button onClick={increment}>increment</button> <button onClick={decrement}>decrement</button> <button onClick={reset}>reset</button> </div> ); };
export default Counter2;
|
兩個組件載入到 App.js
App.js
1 2 3 4 5 6 7 8 9 10 11
| import Counter1 from "./components/Counter1"; import Counter2 from "./components/Counter2";
export default function App() { return ( <div> <Counter1 /> <Counter2 /> </div> ); }
|
可以看到兩個組件的功能相同,但卻寫了兩次,我們可以把相同邏輯的地方抽出來變 custom hook
◆ 調整成 Custom Hook
在 hooks 資料夾新增 useCounter.js
hooks/useCounter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { useState } from "react";
export const useCounter = () => { const [count, setCount] = useState(0);
const increment = () => { setCount((prevCount) => prevCount + 1); };
const decrement = () => { setCount((prevCount) => prevCount - 1); };
const reset = () => { setCount(0); };
return [count, increment, decrement, reset]; };
|
把 Counter1、Counter2 相同邏輯的地方抽出來,並 return 變數方法給 useCounter
components/Counter1.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useCounter } from "../hooks/useCounter";
const Counter1 = () => { const [count, increment, decrement, reset] = useCounter();
return ( <div> <h2>Counter1:{count}</h2> <button onClick={increment}>increment</button> <button onClick={decrement}>decrement</button> <button onClick={reset}>reset</button> </div> ); };
export default Counter1;
|
components/Counter2.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useCounter } from "../hooks/useCounter";
const Counter2 = () => { const [count, increment, decrement, reset] = useCounter();
return ( <div> <h2>Counter2:{count}</h2> <button onClick={increment}>increment</button> <button onClick={decrement}>decrement</button> <button onClick={reset}>reset</button> </div> ); };
export default Counter2;
|
App.js
1 2 3 4 5 6 7 8 9 10 11
| import Counter1 from "./components/Counter1"; import Counter2 from "./components/Counter2";
export default function App() { return ( <div> <Counter1 /> <Counter2 /> </div> ); }
|
這樣就打造好 useCounter 囉!要修改邏輯部份時,不用再去買個組件,直接從 useCounter.js 一次修改完成
範例二:useInterval
現在有兩個計數器,Counter
為正向計數,BackCounter
為反向計數,並載入在 App.js
components/Counter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useEffect, useState } from "react";
const Counter1 = () => { const [count, setCount] = useState(0);
useEffect(() => { const timer = setInterval(() => { if (count < 100) { setCount((count) => count + 1); } }, 100);
return () => { clearInterval(timer); }; }, [count]);
return <h1>Counter1:{count}</h1>; };
export default Counter1;
|
components/BackCounter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useEffect, useState } from "react";
const BackCounter = () => { const [count, setCount] = useState(100);
useEffect(() => { const timer = setInterval(() => { if (count > 0) { setCount((count) => count - 1); } }, 100);
return () => { clearInterval(timer); }; }, [count]);
return <h1>BackCounter:{count}</h1>; };
export default BackCounter;
|
App.js
1 2 3 4 5 6 7 8 9 10 11
| import Counter from "./components/Counter"; import BackCounter from "./components/BackCounter";
export default function App() { return ( <div className="App"> <Counter /> <BackCounter /> </div> ); }
|
情境說明
- 正向計數:從 0 開始每秒 +1,如數字到 100 將會停止
- 反向計數:從 100 開始每秒 -1,如數字到 0 將會停止
我們可以看到,雖然兩個組件邏輯有點不太一樣,但都有使用到 setInterval 的部分,跟 useEffect 的清除函式,這時候可以把 setInterval 抽出來,變成 useInterval
◆ 調整成 Custom Hook
hooks/useInterval.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useEffect, useRef } from "react";
export const useInterval = (callback, delay) => { const savedCallback = useRef();
useEffect(() => { savedCallback.current = callback; }, [callback]);
useEffect(() => { function tick() { savedCallback.current(); }
if (delay !== null) { const timer = setInterval(tick, delay);
return () => clearInterval(timer); } }, [delay]); };
|
將 callback function 和秒數變成參數帶入,並用 useRef、useEffect 確保回傳最新的 function
components/Counter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useState } from "react"; import { useInterval } from "../hooks/useInterval";
const Counter1 = () => { const [count, setCount] = useState(0);
useInterval(() => { if (count < 100) { setCount((count) => count + 1); } }, 1000);
return <h1>Counter1:{count}</h1>; };
export default Counter1;
|
components/BackCounter.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useState } from "react"; import { useInterval } from "../hooks/useInterval";
const BackCounter = () => { const [count, setCount] = useState(100);
useInterval(() => { if (count > 0) { setCount((count) => count - 1); } }, 1000);
return <h1>backCounter:{count}</h1>; };
export default BackCounter;
|
App.js
1 2 3 4 5 6 7 8 9 10 11
| import Counter from "./components/Counter"; import BackCounter from "./components/BackCounter";
export default function App() { return ( <div className="App"> <Counter /> <BackCounter /> </div> ); }
|
如此一來,組件可以使用 useInterval 傳入參數,未來如果也要新增 setInterval 的功能,就可以直接使用 useInterval
結語
Custom Hook 提供了共享邏輯的靈活性,可讀性大幅的提高,也省去重複新增修改的麻煩,相信你已經知道怎麼使用 Custom Hook,打造一個屬於你的 Hook 吧!
Reference
Building Your Own Hooks
React Hooks 系列之8 custom Hook
使用 React Hooks 聲明 setInterval
本文為 IT 鐵人賽系列文 你 React 了嗎? 30 天解鎖 React 技能
🚀實體工作坊分享
最近時賦學苑開了實體體驗課,即使你對程式碼沒有概念也能上手!Lala 會帶你一起做出一個個人品牌形象網站,帶你快速了解前端的開發流程,快跟我們一起玩轉 Web 吧!
🚀線上課程分享
線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。
Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住
如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!
Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。