iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
2
自我挑戰組

React初心者30天的探索之路系列 第 26

[Day 26] React memory leak - 記憶體洩漏

  • 分享至 

  • xImage
  •  

memory leak字面上翻譯就叫做記憶體洩漏,記憶體洩漏會造成什麼問題?大家應該有那種經驗,開太多chrome分頁,電腦開始卡卡的,嚴重點直接當掉,memory leaky的概念類似這樣,程式在運作的時候會佔用記憶體,沒有用到的記憶體如果沒被即時釋放,記憶體佔用就會越來越高,然後你的程式就會崩潰了!

在了解memory leak之前,先來認識javasScript的垃圾回收機制(garbage collection),在記憶體沒有被用到的時候應該要被釋放,但在下面幾種情境下就會造成記憶體洩漏

  1. 全域變數:未經宣告,name會變成全域變數,就不會被回收掉
function test(){ 
    name = "bill"  
}
  1. 閉包:匿名function可以拿到父層作用域的變數值,導致變數無法被回收
function parent(){
    var count = 0 
    return function(){
      console.log('count'+count)
    }
}

3.計時器:setInterval、setTimeout等等沒使用時,如果未清除還是會持續佔用記憶體,所以應該使用clearInterval以及clearTimeout來清除計時器

4.DOM被移除時,監聽事件未被移除(部分老舊瀏覽器

document.getElementById('copy').addEventListener('click',copyFunction)
const target=document.getElementById('copy')
target.parentNode.removeChild(target);

如果有用到計時器但沒有在component銷毀的時候,一併清除計時器的話就會造成記憶體洩漏的問題,然後如果你有很多個計時器的話,瀏覽器應該會崩潰

為了展示計時器忘記清除的狀況下造成的記憶體洩漏問題,寫了一個計時器每秒會自動加一,剛好遇到一個問題,那就是我的數字永遠都停在1,以為setInterval只會執行一遍,但如果看console.log,會發現其實每秒都在執行,只是count永遠都是1

export default () => {
    const [count, setCount] = useState(0)
    useEffect(() => {
        setInterval(() => {
            setCount(count+1)
        }, 1000);
    }, [])

    return (
        <div>count:{count}</div>
    )
}

為什麼會這樣?因為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銷毀時清除計時器

  useEffect(() => {
        const timer = setInterval(() => {
            setCount(count=>count+1)
        }, 1000);
        return () =>{
            clearInterval(timer)
        }
    }, [])

還有兩種比較常見的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銷毀時就取消請求

上一篇
[Day 25] React Portal 任意門
下一篇
[Day 27] 利用React Suspense & React Lazy來優化載入速度
系列文
React初心者30天的探索之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言