ItIron2023
react
昨天我們看了一下一個react hook在render上的常見使用錯誤,未來我們會再次接觸到,建議先把hook的基本使用守則記好,它配合條件渲染會有另一個常見的錯誤發生,這裡我先賣個關子。 我們今天來看一下一個比較不明顯,但不好好處理的話你會榨乾使用者記憶體的錯誤吧!
請看一下這個codesandbox。
就如圖片中所呈現的,這是一個相當簡單的頁面,僅有home & timer兩個頁面做切換,home頁面沒有任何邏輯處理,timer頁面則是你進入後會有個倒數10秒的計時,時間到之後則會將顯示的內容變更為Time's up!
,已結果來說看起來並沒有什麼異常,在useEffect中的callback利用了setInterval很正確的每一妙去遞減timer的值來達到重新渲染的效果,callback中也加入了log讓你知道他有正確在倒數。
不過若你反覆往返這兩個頁面,你會發現log跳的數量很明顯的不正常,請參考下面的gif,這是往返約10次的結果。
log像是失控一般每秒跳了數十次,試著想像一下這不是log而是某個api call,那麽你應該開始感覺到這問題有多嚴重了,請嘗試解釋問題為什麼會發生以及該如何修復。
const CountdownTimer = () => {
const [timeLeft, setTimeLeft] = useState(10);
useEffect(() => {
setInterval(() => {
console.log("Timer tick");
setTimeLeft((prevTime) => prevTime - 1);
}, 1000);
}, []);
if (timeLeft <= 0) {
return <div>Time's up!</div>;
}
return <div>{timeLeft} seconds left</div>;
};
這也是實務上相當常見的錯誤,尤其是react的初學者在使用useEffect時會忽略的點,這邊最重要的點在於你要清楚useEffect是由三個部分所組成的,分別是callback, dependency array & clean up function,前兩者我相信你很熟了,最後的clean up function則是會在組件unmount或是useEffect再次執行之前會被執行,而這個題目的重點就是在它缺少了clean up function,也就是每次這個組件被掛載(mount)時,setInterval都會執行一次,隨著你往返越來越多次,組件被重新掛載的次數也隨之增加,setInterval做出的timer也自然越來越多,最終造成了相同的callback被執行了許多次,時間一久就會因為耗光你瀏覽器的記憶體而讓你的頁面越來越慢、甚至關閉整個瀏覽器。
了解原因後其實解法就很簡單了,你只要照著語法掛上對應的clean up function即可,例如下方的修正就是其中一種做法。
useEffect(() => {
const timer = setInterval(() => {
console.log("Timer tick");
setTimeLeft((prevTime) => prevTime - 1);
}, 1000);
return () => {
clearInterval(timer)
}
}, []);
在這個解法中我們將setInterval回傳的id用一個timer變數存起來,並在每次unmount時清除該timer(這個範例的dependency array為空陣列,因此並不會存在除了unmount之外執行clean up function的情況),這麼一來一切就正常囉!
這類的情況經常發生在timer的使用以及各種監聽器的掛載,例如像是你需要在組件mount時監聽websocket來的message達成即時通訊的效果,若是在離開組件時沒有正確的卸載監聽器就會有許多意想不到的行為出現,嚴重一點的後果就是大量的memory leak最終導致整個頁面停擺,後果聽起來很嚴重,但解法卻意外的簡單對吧?
我們今天算是複習了一下useEffect的基本應用以及構成,各位在組件使用useEffect時、尤其是文章提過的那幾種情況,務必注意是否有正確地掛上clean up function,免得你的頁面在不知不覺間越跑越慢喔!那麼我們明天見,會繼續來看一些渲染時可能面臨的錯誤!
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!