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

尋解
查看mdn 中 # addEventListener的解釋,發(fā)現(xiàn)了一個(gè)神奇的參數(shù):
- signal 可選:
AbortSignal,該AbortSignal的abort()方法被調(diào)用時(shí),監(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ū)域點(diǎn)擊了
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() // 移除事件
}
}如上,在指令銷毀時(shí),觸發(fā)beforeUnmount事件,調(diào)用控制對象控制器對象的abort方法移除事件
注意:當(dāng)我們點(diǎn)擊某塊區(qū)域的時(shí)候,需要先判斷是否在指定的區(qū)域內(nèi),然后再執(zhí)行相應(yīng)的邏輯,這時(shí)要求判斷是否在區(qū)域內(nèi)的點(diǎn)擊事件,要優(yōu)先執(zhí)行,所以要設(shè)置指令的點(diǎn)擊事件在
事件捕獲階段觸發(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ù)定義在指令外,所有指令共用同一個(gè)處理函數(shù),參數(shù)綁定在處理函數(shù)上,類似vue2中依賴收集時(shí)的: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零基礎(chǔ)入門之模板語法與數(shù)據(jù)綁定及Object.defineProperty方法詳解
這篇文章主要介紹了Vue初學(xué)基礎(chǔ)中的模板語法、數(shù)據(jù)綁定、Object.defineProperty方法等基礎(chǔ),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09
詳解如何在vue+element-ui的項(xiàng)目中封裝dialog組件
這篇文章主要介紹了詳解如何在vue+element-ui的項(xiàng)目中封裝dialog組件,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
vue實(shí)現(xiàn)tab欄點(diǎn)擊高亮效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)tab欄點(diǎn)擊高亮效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
解決vue.js提交數(shù)組時(shí)出現(xiàn)數(shù)組下標(biāo)的問題
今天小編就為大家分享一篇解決vue.js提交數(shù)組時(shí)出現(xiàn)數(shù)組下標(biāo)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11

