React memory leak — 記憶體洩漏

Joe Chang
Coding Hot Pot
Published in
Dec 14, 2020
photo by @ihor_malytskyi

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

  1. 假設寫了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銷毀時就取消請求

--

--

Joe Chang
Coding Hot Pot

前端工程師,唯有非常努力,才能看起來毫不費力