熟悉以後應該會很實用。
簡述
最近在上 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 => { 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 最後得回傳 data
、error
和 fetching
這三樣東西。這些東西看起來就是會「跟畫面顯示有關」,所以會用 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 => { 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 這兩種不同的方法。
給你一些提示:
- 這代表我們在用
useFetch
時得多傳入一個參數讓他知道要用 GET / POST
- 如果是 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";
export function useFetch(url, method = "GET") { const [data, setData] = useState(null); const [error, setError] = useState(null); const [fetching, setFetching] = useState(false); const [options, setOptions] = useState(null);
const setPostData = (data) => { setOptions({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); };
useEffect(() => { 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 的話絕對沒問題,但這邊就不示範了,留給你自己來練習。