Vue3 事件:常用用法与开发高频坑点

0 阅读3分钟

前言:

Vue3 项目里,事件处理(@click、@change、@keyup 等)是最常见的内置事件。很多时候开发遇到的相关坑点问题不在"语法不会",而在传参、修饰符、target 区分、异步访问、类型推断这些细节上。本文分享一下日常开发里真实会用到事件的写法,以及那些常见的报了诡异 bug、排查半天的坑。

一、事件的基础写法:

1. 最简单的用法

不传任何参数时,事件处理函数会自动收到原生事件对象作为第一个参数:

<template>
  <button @click="handleClick">点击</button>
</template>

<script setup>
  function handleClick(e) {
    console.log(e) // 原生 Event 对象
  }
</script>

2. 需要自定义参数 + 事件对象

只要传了自定义参数,就必须手动把 $event 一起传进去,否则事件对象会被覆盖掉:

<template>
  <button @click="handleClick('商品A', $event)">点击</button>
</template>

<script setup>
  function handleClick(name, e) {
    console.log(name) // '商品A'
    console.log(e.target) // 事件对象
  }
</script>

这是最经典的坑:带自定义参数却忘了 $event ,函数里拿到的 e 其实是自定义值,根本不是事件对象。

二、常用事件场景:

1. 获取当前点击的元素

<template>
  <div @click="handleClick">
    <span>内容</span>
  </div>
</div>

<script setup>
  function handleClick(e) {
    console.log(e.target) // 真正点击的元素(可能是 span)
    console.log(e.currentTarget) // 绑定事件的那个元素(div)
  }
</script>

target 和 currentTarget 是区分难度最高的两个概念,几乎每个项目都会有人踩坑:

  • e.target:用户实际点击的元素(子元素)
  • e.currentTarget:绑定事件监听的元素(父元素,始终不变)

嵌套结构里,点击 span 时:

  • target → span
  • currentTarget → div

业务上想拿"绑定事件的那个节点",务必用currentTarget。

2. 输入框的输入事件

<template>
  <input @input="handleInput" />
</template>

<script setup>
  function handleInput(e) {
    console.log(e.target.value) // 当前输入的值
  }
</script>

3. 键盘事件(回车、按键判断)

<template>
  <input @keyup="handleKey" />
</template>

<script setup>
  function handleKey(e) {
    if (e.key === 'Enter') {
      console.log('按下了回车')
    }
    if (e.key === 'Escape') {
      console.log('按下了 Esc')
    }
  }
</script>

4. 阻止冒泡和默认行为

优先用修饰符,简单清晰:

<!-- 阻止冒泡 -->
<div @click.stop="handleClick">…</div>

<!-- 阻止默认行为(如表单提交、a 标签跳转) -->
<form @submit.prevent="handleSubmit">…</form>

<!-- 组合 -->
<a href="…" @click.prevent.stop="handleClick">链接</a>

需要在函数里动态判断时,再用事件对象方法:

  function handleClick(e) {
    if (某种条件) {
      e.stopPropagation() // 阻止冒泡
    }
    
    if (另一种条件) {
      e.preventDefault() // 阻止默认行为
    }
  }

三、开发高频坑点汇总:

坑点 1:传了自定义参数却忘了 $event

<template>
  <!-- 错误 -->
  <button @click="handleClick(10)">点击</button>
</template>

<script setup>
  // 你以为 e 是事件对象,其实是 10
  function handleClick(e) {
    console.log(e.target)  // undefined,报错!
  }
</script>

解决:需要事件对象就手动传 $event 。

坑点 2:参数顺序写反

<template>
  <!-- 模板里顺序 -->
  <button @click="handle('A', $event)">点击</button>
</template>

<script setup>
  // 函数里顺序必须和模板完全一致
  function handle(name, e) {
    // 正确:name='A',e=事件对象
  }
</script>

顺序反了不会报错,但取值全乱,属于最隐蔽的坑。

坑点 3:target 和 currentTarget 混用

上面讲过,嵌套 DOM 里两者不同。用错了会导致:

  • 想拿父级数据,却取到子元素的属性(undefined)
  • 样式 / class 改到了错误节点

规则:凡是"绑定事件的那个元素",一律用 currentTarget 。

坑点 4:修饰符和函数内阻止逻辑重复

<template>
  <!-- 用了 .stop -->
  <div @click.stop="handleClick">…</div>
</template>

<script setup>
  function handleClick(e) {
    e.stopPropagation() // 又手动阻止一次
  }
</script>

重复阻止一般不会报错,但会让逻辑混乱、排查困难。建议统一规范:

  • 简单、固定的场景:用模板修饰符
  • 需要条件判断的动态场景:只用函数内方法,别混用

坑点 5:异步回调里直接使用事件对象

  function handleClick(e) {
    setTimeout(() => {
      console.log(e.target)  // 可能拿到 null / undefined
    }, 500)
  }

原生事件对象在事件处理完后会被复用/回收,异步里再访问内容不稳定。

解决:先缓存需要的值。

  function handleClick(e) {
    const el = e.target
    const val = e.target.value
    setTimeout(() => {
      console.log(el, val) // 用缓存值,安全
    }, 500)
  }

坑点 6:键盘事件用 keyCode 已废弃

// 老写法,已废弃
if (e.keyCode === 13) { … }

用现代写法:

if (e.key === 'Enter') { … }
if (e.code === 'Enter') { … }
  • e.key:表示按键的字符 / 名称(如 'Enter'、'a'、'ArrowUp')
  • e.code:表示物理按键位置(如 'KeyA'、'ArrowUp')

区分中英文输入法布局时,用 e.code 更可靠。

坑点 7:事件绑定到组件标签上(组件事件)

给自定义组件用 @click 时,默认监听的是组件自定义事件,不是原生 DOM 事件:

<!-- 这样写,默认不会触发原生 click -->
<MyButton @click="handleClick" />

如果想让事件落到底层原生元素上,需要:

<MyButton @click="handleClick" @click.capture="…" />

或者在组件内部显式 defineEmits:

// MyButton.vue
const emit = defineEmits(['click'])
function onClick(e) {
  emit('click', e)
}

简单记:原生 DOM 元素直接用 @click 没问题;自定义组件上想监听原生事件,要么组件内部 emit ,要么用 .capture 等原生修饰符。

四、事件常用修饰符一览:

image.png

常用按键快捷写法:

<input @keyup.enter="submit" />
<input @keyup.esc="cancel" />
<input @keyup.ctrl.enter="save" />

五、事件处理函数几种写法的取舍:

image.png

六、开发规范建议:

  1. 绑定事件元素的节点操作,一律用 currentTarget,避免嵌套踩坑。
  2. 需要事件对象 + 自定义参数时,别漏 $event ,别写反顺序。
  3. 固定阻止逻辑用修饰符,动态判断用函数内方法,别混用。
  4. 异步里先缓存事件对象的属性,别直接延迟访问。
  5. 键盘判断用 e.key / e.code ,别用废弃的 keyCode 。
  6. 自定义组件的事件按"组件事件"处理,要原生事件就内部 emit 或加原生修饰符。