vue3中addEventListener的用法詳解
緣起
vue3中定義全局指令時,往往會碰到一個問題:事件無法解綁。為什么會這樣,因為通常在指令的mounted鉤子中綁定事件,事件處理函數(shù)也定義在mounted中。在指令的unmounted鉤子函數(shù)中無法獲取到定義的事件處理函數(shù),所以綁定的事件無法銷毀,尤其當(dāng)這個事件是綁定在父組件,或者body上時,無法解綁事件,指令大量使用會造成性能問題。
removeEventListener方法可以刪除使用addEventListener方法添加的事件。使用事件類型,事件偵聽器函數(shù)本身,以及可能影響匹配過程的各種可選擇的選項的組合來標(biāo)識要刪除的事件偵聽器,簡單說要使用removeEventListener移除事件,需要事件處理函數(shù)和添加時的事件處理函數(shù)是同一個。
如下一個判斷鼠標(biāo)點擊區(qū)域是否在指定區(qū)域內(nèi)的指令:
// 使用場景:僅在點擊select組件外部區(qū)域,收起下拉選項
import { Directive } from 'vue'
export const clickOutside: Directive = {
mounted(el: Element, { value }) {
const controller = new AbortController()
controllerMap.set(el, controller)
document.body.addEventListener(
'click',
(e) => {
// 在外部區(qū)域點擊了
if (!el.contains(e.target as Element)) {
typeof value === 'function' && value()
}
}
)
}
}如上指令,在銷毀時無法進(jìn)行事件的移除,導(dǎo)致事件處理函數(shù)無法被銷毀,占用內(nèi)存無法釋放:

尋解
查看mdn 中 # addEventListener的解釋,發(fā)現(xiàn)了一個神奇的參數(shù):
- signal 可選:
AbortSignal,該AbortSignal的abort()方法被調(diào)用時,監(jiān)聽器會被移除。
這不就可以完美解決了么,立馬優(yōu)化指令:
import { Directive } from 'vue'
const controllerMap = new WeakMap<Element, AbortController>()
export const clickOutside: Directive = {
mounted(el: Element, { value }) {
const controller = new AbortController()
controllerMap.set(el, controller)
document.body.addEventListener(
'click',
(e) => {
// 在外部區(qū)域點擊了
if (!el.contains(e.target as Element)) {
typeof value === 'function' && value()
}
},
{
capture: true, // 為了防止v-if切換導(dǎo)致的判斷誤差,在捕獲階段觸發(fā)事件
signal: controller.signal
}
)
},
beforeUnmount(el) {
const controller = controllerMap.get(el)
controller?.abort() // 移除事件
}
}如上,在指令銷毀時,觸發(fā)beforeUnmount事件,調(diào)用控制對象控制器對象的abort方法移除事件
注意:當(dāng)我們點擊某塊區(qū)域的時候,需要先判斷是否在指定的區(qū)域內(nèi),然后再執(zhí)行相應(yīng)的邏輯,這時要求判斷是否在區(qū)域內(nèi)的點擊事件,要優(yōu)先執(zhí)行,所以要設(shè)置指令的點擊事件在
事件捕獲階段觸發(fā), 即:capture: true
場景
如下場景:

<template>
<div class="edit-cell" v-clickOutside="clickOutside">
<template v-if="!edit">
<a-tooltip :title="value" v-if="value.length > 20">
<div class="text text-ellipsis">
{{ value || '--' }}
</div>
</a-tooltip>
<div v-else>{{ value || '--' }}</div>
<edit-outlined class="editable-cell-icon icon" @click="enableEdit" />
</template>
<template v-else>
<component
:is="EditComponent"
ref="editComponentRef"
:placeholder="$t('correction.create.modal.form.projectName.err')"
class="text-edit"
:maxLength="200"
@input="change"
style="height: 60px; resize: none"
:value="currentValue"
@pressEnter="pressEnter"
/>
<check-outlined class="editable-cell-icon-check icon" @click="save" />
</template>
</div>
</template>
<script lang="ts" setup>
import { EditOutlined, CheckOutlined } from '@ant-design/icons-vue'
const props = defineProps({
value: {
type: String,
default: ''
},
type: {
type: String as PropType<'input' | 'textarea'>,
default: 'input'
}
})
const emits = defineEmits<{
change: [value: string]
}>()
const currentValue = ref<string>('')
const EditComponent = computed(() =>
props.type === 'input' ? 'NWInput' : 'ATextarea'
)
const edit = ref<boolean>(false)
const change = ({ target }) => {
currentValue.value = target?.value ?? ''
}
const pressEnter = () => {
emits('change', currentValue.value)
edit.value = false
}
const save = () => {
emits('change', currentValue.value)
edit.value = false
}
const editComponentRef = shallowRef()
const enableEdit = () => {
currentValue.value = props.value
edit.value = true
nextTick(() => {
editComponentRef.value.focus()
})
}
const clickOutside = () => {
edit.value = false
}
</script>
<style lang="less" scoped>
.edit-cell {
display: flex;
justify-content: flex-start;
align-items: center;
.text-edit,
.text {
flex: 1;
}
&:hover {
.icon {
visibility: visible;
}
}
.icon {
visibility: hidden;
}
}
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: left !important;
}
</style>另一種思路
可以將處理函數(shù)定義在指令外,所有指令共用同一個處理函數(shù),參數(shù)綁定在處理函數(shù)上,類似vue2中依賴收集時的:Dep.target,將當(dāng)前處理的watcher存儲在Dep函數(shù)的target屬性中,當(dāng)然這種方式只適用于串行的場景
到此這篇關(guān)于vue3中addEventListener的用法詳解的文章就介紹到這了,更多相關(guān)vue3 addEventListener用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中iframe使用以及結(jié)合postMessage實現(xiàn)跨域通信
這篇文章主要介紹了vue中iframe使用以及結(jié)合postMessage實現(xiàn)跨域通信方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
詳解VueRouter進(jìn)階之導(dǎo)航鉤子和路由元信息
本篇文章主要介紹了詳解VueRouter進(jìn)階之導(dǎo)航鉤子和路由元信息,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09
Vue3.0數(shù)據(jù)響應(yīng)式原理詳解
這篇文章主要介紹了Vue3.0數(shù)據(jù)響應(yīng)式原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
axios中如何進(jìn)行同步請求(async+await)
這篇文章主要介紹了axios中如何進(jìn)行同步請求(async+await),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
如何解決element-ui動態(tài)加載級聯(lián)選擇器默認(rèn)選中問題
這篇文章主要介紹了如何解決element-ui動態(tài)加載級聯(lián)選擇器默認(rèn)選中問題,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-09-09

