Lala Code

Lala 的前端大補帖,歡迎一起鑽研前端技術😊

0%

【DAY 21】Custom Hook - 客製你自己的 Hook

cover

認識 Custom Hook

當有多個組件有相同的邏輯,但卻重複寫了好幾次,這時候可以將相同邏輯的地方,抽出來做成一個共用 function,方便我們使用。

Custom Hook 會自然遵循 Hook 設計的規範,且所有內部的 state 和 effect 都是完全獨立的。




Custom Hook 規則

必須以 use 做開頭,方便開發者一眼就知道這是可以使用的 Hook,Lint 工具也會自動檢查是否違反 Hook 規則,如:

1
2
3
useDocumentTitle
useCounter
useInterval



Custom Hook 範例

以下會有兩個範例來做解說

  • useCounter
  • useInterval


範例一: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





調整成 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 將會停止

custom hook

我們可以看到,雖然兩個組件邏輯有點不太一樣,但都有使用到 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 技能




Hey!想學習更多前端知識嗎?

最近 Lala 開了前端課程 👉【實地掌握RWD - 12小時新手實戰班】👈
無論您是 0 基礎新手,又或是想學 RWD 的初學者,
我們將帶你從零開始,深入了解並掌握 RWD 響應式網頁設計的核心技術,快來一起看看吧 😊



🚀線上課程分享

線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。

Hahow

Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住



六角學院

如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!


Udemy

Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。
❤️