30 天的旅途前段會來回頭認識一些基本的 React Hook,繼 useState 再來就是 useEffect。
useEffect 如其名,專門拿來處理 Side Effect,不論你想要串接API,操作DOM,或是想執行非同步function(像是setTimeout) 通通都在這邊運作,那就來看看怎麼使用吧!
import {useEffect} from 'react'
useEffect(() => {
//執行的動作
},[])
useEffect
會在畫面 render 完成之後執行一些動作,根據使用方式執行方法也略有不同:
useEffect(() => {
console.log("每次都有我")
})
//mount
console.log("每次都有我")
//update
console.log("每次都有我")
console.log("每次都有我")
console.log("每次都有我")
...
clean-up
fucntion:useEffect(() => {
console.log("啊!水灑了...")
return () => console.log("我來清理!")
})
//第一次render後
console.log("啊!水灑了...")
//後續render
console.log("我來清理!")
console.log("啊!水灑了...")
//後續render
console.log("我來清理!")
console.log("啊!水灑了...")
//後續render
console.log("我來清理!")
console.log("啊!水灑了...")
...
[]
dependecies 時:useEffect
,當提供的值有所改變時,就會執行,反之若甚麼都不提供(如下),只會執行最初的一次useEffect(() => {
console.log("你只會看見我一次")
}, [])
//第一次render後
console.log("你只會看見我一次")
clean-up
與[]
時:useEffect(() => {
console.log("你只會看見我一次")
return () => console.log("再見!")
},[])
//第一次render後
console.log("你只會看見我一次")
//後續render
//nothing happened
//unmount
console.log("再見!")
前一篇中我們使用了 useState 來記錄 player 的分數,現在我們加入 useEffect 來看看當分數改變時,render 之間 useEffect 是如何輸出的!
如果覺得眼花,可以先把
<StrictMode/>
以及局部的useEffect
給註解掉
React.useEffect(() => {
console.log("[Panel]01 useEffect: Mounted!");
return () => console.log("[Panel]01 useEffect: Cleaning up!");
});
React.useEffect(() => {
console.log("[Panel]02 useEffect: Mounted!");
return () => console.log("[Panel]02 useEffect: Cleaning up!");
}, []);
React.useEffect(() => {
console.log("[Panel]03 useEffect: Mounted!");
return () => console.log("[Panel]03 useEffect: Cleaning up!");
}, [score]);
當進入畫面之後,預期的輸出會是:
console.log("[Panel]01 useEffect: Mounted!");
console.log("[Panel]02 useEffect: Mounted!");
console.log("[Panel]03 useEffect: Mounted!");
當 score 變更時:
console.log("[Panel]01 useEffect: Cleaning up!");
console.log("[Panel]03 useEffect: Cleaning up!");
console.log("[Panel]01 useEffect: Mounted!");
console.log("[Panel]03 useEffect: Mounted!");
常見的情況就是用來與API互動。
可以在 Demo 的 api/player
找到模仿的 api 以及假資料。
假設今天活動結束後,我們要瀏覽所有 Player 的分數:
const [list, setList] = useState([]);
const [isPending, setIsPending] = useState(true);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
setIsPending(true);
setList([]);
const data = await getPlayerList();
setList(data);
setIsPending(false);
};
然後教練說:「讓我們來看看有誰分數高於10吧!」(哪來的教練?
於是我們建立了 minScore 當作假API的 query:
const [list, setList] = useState([]);
const [isPending, setIsPending] = useState(true);
const [minScore, setMinScore] = useState(0);
useEffect(() => {
fetchData();
}, [minScore]); //把 minScore 當作 re-fetch 的 trigger
const fetchData = async () => {
setIsPending(true);
setList([]);
const data = await getPlayerList({ minScore }); //如同api query
setList(data);
setIsPending(false);
};
但這樣延伸出了一個問題:「只要使用者不停地輸入,fetchData()
就會一直執行」
教練:「不能給我隨便按ㄛ?」
我們可以加入 debounce / input disabled 的方式來避免這種可怕的情況
const [list, setList] = useState([]);
const [isPending, setIsPending] = useState(true);
const [minScore, setMinScore] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
fetchData();
}, 1000);
return () => clearTimeout(timeout);
}, [minScore]);
const fetchData = async () => {
setIsPending(true);
setList([]);
const data = await getPlayerList({ minScore });
setList(data);
setIsPending(false);
};
...
<input disabled={isPending} />
在 useEffect
加入 debounce 的方法,當使用者輸入內容時,會觸發 setTimeout 並在一秒後執行 fetchData()
,若使用者輸入很快時,useEffect
的 clean-up function 也會先行執行,以避免太多不必要的 fetching;也使用了 isPending
來提供畫面的 loading 呈現,來告知使用者資料正在抓取中。
單純就是開發過程中如果忘記加上 [] 就會...
useEffect(() => {
fetchData();
});
//OR
useEffect(() => {
setState(state + 1)
}, [state]); //如果加上了但放錯內容就會...(情境提供參考^_^)
後端:「難道要被攻擊了嗎OAO」
若本系列有錯誤的內容,歡迎隨時跟我告知。