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-09Vue3.0數(shù)據(jù)響應(yīng)式原理詳解
這篇文章主要介紹了Vue3.0數(shù)據(jù)響應(yīng)式原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10axios中如何進(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