React memory leak — 記憶體洩漏
memory leak字面上翻譯就叫做記憶體洩漏,記憶體洩漏會造成什麼問題?大家應該有那種經驗,開太多chrome分頁,電腦開始卡卡的,嚴重點直接當掉,memory leaky的概念類似這樣,程式在運作的時候會佔用記憶體,沒有用到的記憶體如果沒被即時釋放,記憶體佔用就會越來越高,然後你的程式就會崩潰了!
在了解memory leak之前,先來認識javasScript的垃圾回收機制(garbage collection),在記憶體沒有被用到的時候應該要被釋放,但在下面幾種情境下就會造成記憶體洩漏
1.全域變數:未經宣告,name會變成全域變數,就不會被回收掉
function test(){ name="bill" }
2.閉包: 匿名function可以拿到父層作用域的變數值,導致變數無法被回收
function parent(){var count =0 return function(){console.log('count'+count)}}
3.計時器 setInterval 、setTimeout等等沒使用時,如果未清除還是會持續佔用記憶體,所以應該使用clearInterval以及clearTimeout來清除計時器
3.計時器:即使copy這個dom被移除了,但是對dom的參考一直都在,所以計時器持續執行就無法將copy回收(即將刪除
setInterval(() => {
const btn = document.getElementById('copy');
btn.innerHTML = varible + 1
}, 1000);
4.DOM被移除時,監聽事件未被移除(部分老舊瀏覽器
document.getElementById('copy').addEventListener('click',copyFunction)const target=document.getElementById('copy')
target.parentNode.removeChild(target);
如果有用到計時器但沒有在component銷毀的時候,一併清除計時器的話就會造成記憶體洩漏的問題,然後如果你有很多個計時器的話,瀏覽器應該會崩潰
為了展示計時器忘記清除的狀況下造成的記憶體洩漏問題,寫了一個計時器每秒會自動加一,剛好遇到一個問題,那就是我的數字永遠都停在1,以為setInterval只會執行一遍,但如果看console.log,會發現其實每秒都在執行,只是count永遠都是1
為什麼會這樣?因為setInterval取到的count是最初始的0,是的,這是閉包的陷阱,如果要解決這個問題可以在setState傳入一個function,count=>count+1,該函式就可以取得最新的count
useEffect(() => { setInterval(() => { setCount(count=>count+1) }, 1000);}, [])
或者將count傳入useEffect,當count變化時就會觸發useEffect
useEffect(() => { setInterval(() => { setCount(count + 1) }, 1000);}, [count])
當計時器終於正常運作的時候,我在外部component寫了一個判斷,三秒過後變數show會變成false,此時就會銷毀計時器component
{show ? <MemoryLeak /> :null}
三秒過後會發現計時器消失了的同時,也出現了這樣的錯誤,看到關鍵字 memory leak
要解決這樣的問題,就要記得在useeffect內回傳clean up函式,就會在component銷毀時清除計時器
還有兩種比較常見的memory leak
- 假設寫了setTimeout 設定五秒後setState,但component在五秒之前就已經銷毀了,此時五秒一到執行setState就會造成memory leak
2. 有個情境是call 拿到資料後setState,如果用了非同步請求,在api比較慢的狀況下,response還沒回來時,component被銷毀後response才回來觸發了setState ,造成memory leak
會造成react memory leak通常就是在已經銷毀的component上進行setState
解決的memory leak方法
解決settimeout、setinterval造成的memory leak
- class component務必在componentWillUnmount階段移除計時器
- function component要記得在useEffect裡面return清除計時器的function
解決非同步造成的memory leak
- class component在constructor裡宣告一個變數為false,在componentDidMount階段設為true,接著在api請求回來那邊多加一個判斷,如果變數非true(代表component已經銷毀),就return中斷(如果有更好的做法歡迎告知)
- function component 在useEffect裡傳入 clean up函式, 當component銷毀時就取消請求