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中iframe使用以及结合postMessage实现跨域通信

    vue中iframe使用以及结合postMessage实现跨域通信

    这篇文章主要介绍了vue中iframe使用以及结合postMessage实现跨域通信方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • 在vue使用echarts报错:invalid dom问题

    在vue使用echarts报错:invalid dom问题

    这篇文章主要介绍了在vue使用echarts报错:invalid dom问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Element-UI清空表单及验证不生效的问题解决

    Element-UI清空表单及验证不生效的问题解决

    本文主要介绍了Element-UI清空表单及验证不生效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • 详解VueRouter进阶之导航钩子和路由元信息

    详解VueRouter进阶之导航钩子和路由元信息

    本篇文章主要介绍了详解VueRouter进阶之导航钩子和路由元信息,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • Vue.js与Flask/Django后端配合方式

    Vue.js与Flask/Django后端配合方式

    在现代Web开发中,Vue.js与Flask或Django配合使用,实现前后端分离,提高开发效率和应用性能,本文介绍了整合Vue.js和Flask/Django的步骤,包括环境搭建、API编写、项目配置,以及生产部署,此架构不仅加快了开发进程,还提高了项目的可维护性和可扩展性
    2024-09-09
  • Vue3.0数据响应式原理详解

    Vue3.0数据响应式原理详解

    这篇文章主要介绍了Vue3.0数据响应式原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • 在vue中动态修改css其中一个属性值操作

    在vue中动态修改css其中一个属性值操作

    这篇文章主要介绍了在vue中动态修改css其中一个属性值操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • vue-property-decorator用法详解

    vue-property-decorator用法详解

    这篇文章主要介绍了vue-property-decorator用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • axios中如何进行同步请求(async+await)

    axios中如何进行同步请求(async+await)

    这篇文章主要介绍了axios中如何进行同步请求(async+await),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • 如何解决element-ui动态加载级联选择器默认选中问题

    如何解决element-ui动态加载级联选择器默认选中问题

    这篇文章主要介绍了如何解决element-ui动态加载级联选择器默认选中问题,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-09-09

最新评论