React-來捏一個 Custom hook

熟悉以後應該會很實用。

簡述

最近在上 Build Web Apps with React & Firebase 這門課,裡面有介紹到如何利用 hook 來建立一個 custom hook。

我覺得這東西還蠻實用的,但我還不是很熟,所以想透過這篇筆記來多做一點練習。

用 custom hook 有什麼好處?

在學任何一項東西以前,試著先問自己「為什麼要用這個東西?」是比較好的觀念,才不會學了一大堆東西後卻完全不知道用途。

首先,我們拿一個最常見的例子來舉例:串接 API

最簡單的串接方式是透過 fetch,此時你的腦袋中應該會有這樣的 code:

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
function App () {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [fetching, setFetching] = useState(false)

useEffect(() => {
setFetching(true)
fetch(url)
.then(response => {
// 強制進入 catch 區塊
if (!response.ok) throw new Error(response.status)
return response.json()
})
.then(json => {
setData(json)
setFetching(false)
})
.catch(error => {
console.log(error.message)
setError('Can not fetch data')
setFetching(false)
})
}, [])

return ...
}

看起來就是非常標準的寫法,這樣有什麼問題嗎?

問這問題以前,要先問問看「你有多少元件會用到 fetch?」,如果說就只有一個 App 會用到的話當然沒什麼問題。

可是如果有很多個呢?

那你就得在每一個元件上都寫上那段 boilerplate,這樣子聽起來就不是那麼好了吧?

但如果我們自己寫了一個專門用來處理 fetch 的 custom hook,就可以改寫成這樣子:

1
2
3
4
5
import { useFetch } from "hooks/useFetch"
function App () {
const { data, error, fetching } = useFetch(url)
return ...
}

有沒有突然覺得世界變美好了 XD,這個就是 custom hook 的威力。

回歸正題,custom hook 的主要用意是用來「把邏輯抽出去,讓元件只要專注在 render 這件事情上」,可以想成是另一種 HOC(Higher Order Component) 的感覺。

實作 custom hook

這邊會以剛剛的範例來實作,所以如果忘記的話可以拉回去複習。

順道一提,在寫 custom hook 前可以先思考看看「我希望最後會怎麼用這個 hook?」,這樣會讓整個思路更清晰一些。

再貼一次剛剛的 code,我希望最後會長這樣:

1
2
3
4
5
import { useFetch } from "hooks/useFetch"
function App () {
const { data, error, fetching } = useFetch(url)
return ...
}

也就是說這個 hook 最後得回傳 dataerrorfetching 這三樣東西。這些東西看起來就是會「跟畫面顯示有關」,所以會用 useState 來實作。

接下來我會直接附 code,因為你看完再仔細思考一下,就會發現只是「把原本寫在元件裡的東西抽出來而已」,所以我就不解釋太多了。

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
import { useState, useEffect } from "react"

export function useFetch (url) {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [fetching, setFetching] = useState(false)

useEffect(() => {
setFetching(true)
fetch(url)
.then(response => {
// 強制進入 catch 區塊
if (!response.ok) throw new Error(response.status)
return response.json()
})
.then(json => {
setData(json)
setFetching(false)
})
.catch(error => {
console.log(error.message)
setError('Can not fetch data')
setFetching(false)
})
}, [])

return {data, error, fetching}
}

這樣就完成囉!接著就能在其他元件透過 useFetch 來使用了。

如果有任何疑問的話都可以到我寫的 範例 來玩玩看。

延伸:我不只要 GET,我還想要 POST

這個是上面的延伸練習,你可以試著先自己練習看看,要怎麼做才可以讓 useFetch 同時支援 GET 跟 POST 這兩種不同的方法。

給你一些提示:

  1. 這代表我們在用 useFetch 時得多傳入一個參數讓他知道要用 GET / POST
  2. 如果是 POST 的話會需要傳入資料(body)。這個會有兩種做法,一種是當作參數傳給 useFetch,另一種是在 useFetch 中定義新的 state。你可以想到第二種該怎麼做嗎?

想好的話就可以往下看解答了:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import { useState, useEffect } from "react";

// 如果沒有傳,把預設值設為 GET
export function useFetch(url, method = "GET") {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [fetching, setFetching] = useState(false);
// 新增一個用來儲存資料的 state
const [options, setOptions] = useState(null);

// 為了讓元件可以更新 options,新開一個 setter
const setPostData = (data) => {
setOptions({
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
});
};

useEffect(() => {
// 根據 options 來決定執行哪一段 fetch
if (method === "GET") {
setFetching(true);
fetch(url)
.then((response) => {
if (!response.ok) throw new Error(response.status);
return response.json();
})
.then((json) => {
setData(json);
setFetching(false);
})
.catch((error) => {
console.log(error.message);
setError("Can not fetch data");
setFetching(false);
});
}
if (method === "POST" && options) {
setFetching(true);
fetch(url)
.then((response) => {
if (!response.ok) throw new Error(response.status);
return response.json();
})
.then((json) => {
setData(json);
setFetching(false);
})
.catch((error) => {
console.log(error.message);
setError("Can not send request");
setFetching(false);
});
}
}, [url, options]);

return { data, error, fetching, setPostData };
}

簡單來說就是判斷 method 來執行對應的 fetch 就行了,不太懂的話可以到這邊來看範例

另外如果你覺得這樣子很攏長(fetch 的部分),想包裝成 function 的話絕對沒問題,但這邊就不示範了,留給你自己來練習。

react-nprogress 懶人包 一個 import 的小技巧
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×