在 Vue 中正確移除事件監(jiān)聽的核心是 “在組件生命周期的合適時機,用相同的回調(diào)函數(shù)引用移除監(jiān)聽”,需結(jié)合 Vue 的生命周期鉤子(如onUnmounted、beforeDestroy)和函數(shù)引用穩(wěn)定性來實現(xiàn),避免因監(jiān)聽殘留導致內(nèi)存泄漏。以下是針對 Vue 3 和 Vue 2 的具體實現(xiàn)方法及避坑指南:
Vue 3 的組合式 API 通過setup函數(shù)或<script setup>組織代碼,需在組件掛載時添加監(jiān)聽,在組件卸載前(onUnmounted鉤子)移除,同時確保回調(diào)函數(shù)引用唯一。
<template>
<div>監(jiān)聽窗口大小變化</div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue';
// 1. 定義具名回調(diào)函數(shù)(確保整個組件生命周期內(nèi)引用唯一)
function handleResize() {
console.log('窗口大小變化:', window.innerWidth);
}
// 2. 組件掛載時添加事件監(jiān)聽
onMounted(() => {
window.addEventListener('resize', handleResize);
});
// 3. 組件卸載時移除監(jiān)聽(關(guān)鍵步驟)
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
若監(jiān)聽的是組件內(nèi)的 DOM 元素(如自定義按鈕、列表),需先通過ref獲取 DOM 引用,再添加 / 移除監(jiān)聽:
<template>
<button ref="myButton">點擊按鈕</button>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
// 獲取DOM引用
const myButton = ref(null);
// 定義回調(diào)函數(shù)
function handleClick() {
console.log('按鈕被點擊');
}
onMounted(() => {
// 確保DOM已掛載(myButton.value存在)
myButton.value.addEventListener('click', handleClick);
});
onUnmounted(() => {
// 組件卸載前移除監(jiān)聽
myButton.value.removeEventListener('click', handleClick);
});
</script>
若回調(diào)函數(shù)依賴 Vue 的響應式數(shù)據(jù)(如ref/reactive),需確保函數(shù)引用穩(wěn)定,避免因數(shù)據(jù)更新導致函數(shù)重新創(chuàng)建(可配合useCallback緩存):
<template>
<div>計數(shù):{{ count }}</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, useCallback } from 'vue';
const count = ref(0);
// 用useCallback緩存回調(diào)函數(shù),依賴變化時才重新創(chuàng)建
const handleScroll = useCallback(() => {
// 依賴響應式數(shù)據(jù)count
console.log('滾動時計數(shù):', count.value);
}, [count]); // 當count變化時,函數(shù)重新創(chuàng)建
onMounted(() => {
window.addEventListener('scroll', handleScroll);
});
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
});
</script>
Vue 2 的選項式 API 通過mounted添加監(jiān)聽,在beforeDestroy(或destroyed)鉤子中移除,回調(diào)函數(shù)通常定義在methods中以保證引用穩(wěn)定。
<template>
<div>監(jiān)聽滾動事件</div>
</template>
<script>
export default {
methods: {
// 回調(diào)函數(shù)定義在methods中,引用唯一
handleScroll() {
console.log('頁面滾動了');
}
},
mounted() {
// 組件掛載后添加監(jiān)聽
window.addEventListener('scroll', this.handleScroll);
},
beforeDestroy() {
// 組件銷毀前移除監(jiān)聽(關(guān)鍵)
window.removeEventListener('scroll', this.handleScroll);
}
};
</script>
通過$refs獲取 DOM 元素,在mounted中添加監(jiān)聽,beforeDestroy中移除:
<template>
<div ref="content">內(nèi)容區(qū)域</div>
</template>
<script>
export default {
methods: {
handleClick(e) {
console.log('內(nèi)容區(qū)域被點擊', e.target);
}
},
mounted() {
// 確保DOM已加載(this.$refs.content存在)
this.$refs.content.addEventListener('click', this.handleClick);
},
beforeDestroy() {
this.$refs.content.removeEventListener('click', this.handleClick);
}
};
</script>
- 錯誤示例:使用匿名函數(shù)或在生命周期內(nèi)動態(tài)創(chuàng)建函數(shù),導致
removeEventListener無法匹配引用:
onMounted(() => {
window.addEventListener('scroll', () => { console.log('滾動'); });
});
onUnmounted(() => {
window.addEventListener('scroll', () => { console.log('滾動'); });
});
- 解決方案:始終使用具名函數(shù)(如
function handleXXX())或通過useCallback(Vue 3)/methods(Vue 2)確保引用穩(wěn)定。
- 錯誤示例:
addEventListener的第三個參數(shù)(useCapture)為true時,移除時未傳入相同參數(shù),導致移除失敗:
window.addEventListener('click', handleClick, true);
window.removeEventListener('click', handleClick);
- 解決方案:移除時嚴格保持
useCapture參數(shù)與添加時一致:
window.removeEventListener('click', handleClick, true);
- 問題:若組件內(nèi) DOM 通過
v-if銷毀,可能導致removeEventListener時 DOM 已不存在(如myButton.value為null)。
- 解決方案:移除前先判斷 DOM 是否存在:
onUnmounted(() => {
if (myButton.value) {
myButton.value.removeEventListener('click', handleClick);
}
});
- 問題:使用第三方庫(如 ECharts、地圖庫)時,若庫內(nèi)部綁定了事件,需按其文檔調(diào)用銷毀方法。
- 解決方案:在組件卸載時調(diào)用庫的銷毀函數(shù),避免內(nèi)部監(jiān)聽殘留:
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
// 第三方庫可能內(nèi)部綁定了事件
chartInstance.on('click', (params) => { /* 處理點擊 */ });
});
onUnmounted(() => {
// 調(diào)用庫的銷毀方法,內(nèi)部會自動移除事件監(jiān)聽
if (chartInstance) {
chartInstance.dispose();
}
});
</script>
- 定義穩(wěn)定回調(diào):用具名函數(shù)、
methods(Vue 2)或useCallback(Vue 3)確;卣{(diào)函數(shù)引用唯一。
- 匹配生命周期:在
mounted(Vue 2)/onMounted(Vue 3)中添加監(jiān)聽,在beforeDestroy(Vue 2)/onUnmounted(Vue 3)中移除。
- 檢查參數(shù)與 DOM:確保
removeEventListener的參數(shù)(回調(diào)、useCapture)與添加時一致,且操作 DOM 前判斷其是否存在。
遵循以上步驟,可徹底避免 Vue 組件中因事件監(jiān)聽未移除導致的內(nèi)存泄漏問題。 |