vue3中addEventListener的用法详解

 更新时间:2023年08月23日 09:32:39   作者:远山无期  
vue3中定义全局指令时,往往会碰到一个问题:事件无法解绑,为什么会这样,因为通常在指令的mounted钩子中绑定事件,事件处理函数也定义在mounted中,本文讲给大家讲讲vue3中addEventListener的妙用,需要的朋友可以参考下

缘起

vue3中定义全局指令时,往往会碰到一个问题:事件无法解绑。为什么会这样,因为通常在指令的mounted钩子中绑定事件,事件处理函数也定义在mounted中。在指令的unmounted钩子函数中无法获取到定义的事件处理函数,所以绑定的事件无法销毁,尤其当这个事件是绑定在父组件,或者body上时,无法解绑事件,指令大量使用会造成性能问题。

removeEventListener 方法可以删除使用 addEventListener 方法添加的事件。使用事件类型,事件侦听器函数本身,以及可能影响匹配过程的各种可选择的选项的组合来标识要删除的事件侦听器,简单说要使用removeEventListener移除事件,需要事件处理函数和添加时的事件处理函数是同一个。

如下一个判断鼠标点击区域是否在指定区域内的指令:

// 使用场景:仅在点击select组件外部区域,收起下拉选项
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) => {
        // 在外部区域点击了
        if (!el.contains(e.target as Element)) {
          typeof value === 'function' && value()
        }
      }
    )
  }
}

如上指令,在销毁时无法进行事件的移除,导致事件处理函数无法被销毁,占用内存无法释放:

寻解

查看mdn# addEventListener的解释,发现了一个神奇的参数:

  • signal 可选: AbortSignal,该 AbortSignalabort() 方法被调用时,监听器会被移除。

这不就可以完美解决了么,立马优化指令:

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) => {
        // 在外部区域点击了
        if (!el.contains(e.target as Element)) {
          typeof value === 'function' && value()
        }
      },
      {
        capture: true, // 为了防止v-if切换导致的判断误差,在捕获阶段触发事件
        signal: controller.signal
      }
    )
  },
  beforeUnmount(el) {
    const controller = controllerMap.get(el)
    controller?.abort() // 移除事件
  }
}

如上,在指令销毁时,触发beforeUnmount事件,调用控制对象控制器对象abort方法移除事件

注意:当我们点击某块区域的时候,需要先判断是否在指定的区域内,然后再执行相应的逻辑,这时要求判断是否在区域内的点击事件,要优先执行,所以要设置指令的点击事件在事件捕获阶段触发, 即: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>
      &nbsp;
      <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"
      />
      &nbsp;
      <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>

另一种思路

可以将处理函数定义在指令外,所有指令共用同一个处理函数,参数绑定在处理函数上,类似vue2中依赖收集时的:Dep.target,将当前处理的watcher存储在Dep函数的target属性中,当然这种方式只适用于串行的场景

到此这篇关于vue3中addEventListener的用法详解的文章就介绍到这了,更多相关vue3 addEventListener用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vue零基础入门之模板语法与数据绑定及Object.defineProperty方法详解

    Vue零基础入门之模板语法与数据绑定及Object.defineProperty方法详解

    这篇文章主要介绍了Vue初学基础中的模板语法、数据绑定、Object.defineProperty方法等基础,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-09-09
  • vue中使用微信公众号js-sdk踩坑记录

    vue中使用微信公众号js-sdk踩坑记录

    这篇文章主要介绍了vue中使用微信公众号js-sdk踩坑记录,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • Vue自定义属性实例分析

    Vue自定义属性实例分析

    这篇文章主要介绍了Vue自定义属性,结合实例形式分析了vue.js自定义属性相关原理、实现方法及操作注意事项,需要的朋友可以参考下
    2019-02-02
  • el-table嵌套el-popover处理卡顿的解决

    el-table嵌套el-popover处理卡顿的解决

    本文主要介绍了el-table嵌套el-popover处理卡顿的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • 详解如何在vue+element-ui的项目中封装dialog组件

    详解如何在vue+element-ui的项目中封装dialog组件

    这篇文章主要介绍了详解如何在vue+element-ui的项目中封装dialog组件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • vue中如何通过函数传参数

    vue中如何通过函数传参数

    这篇文章主要介绍了vue中如何通过函数传参数问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • vue实现tab栏点击高亮效果

    vue实现tab栏点击高亮效果

    这篇文章主要为大家详细介绍了vue实现tab栏点击高亮效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-08-08
  • 解决vue.js提交数组时出现数组下标的问题

    解决vue.js提交数组时出现数组下标的问题

    今天小编就为大家分享一篇解决vue.js提交数组时出现数组下标的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • vue 解决IOS10低版本白屏的问题

    vue 解决IOS10低版本白屏的问题

    这篇文章主要介绍了vue 解决IOS10低版本白屏的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • 3种vue路由传参的基本模式

    3种vue路由传参的基本模式

    这篇文章主要为大家详细介绍了vue路由传参的3种基本模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02

最新评论