前言:
Vue3 项目里,事件处理(@click、@change、@keyup 等)是最常见的内置事件。很多时候开发遇到的相关坑点问题不在"语法不会",而在传参、修饰符、target 区分、异步访问、类型推断 这些细节上。本文分享一下日常开发里真实会用到事件的写法,以及那些常见的报了诡异 bug、排查半天的坑。
一、事件的基础写法:
1. 最简单的用法
不传任何参数时,事件处理函数会自动收到原生事件对象作为第一个参数:
xml
<template>
<button @click="handleClick">点击</button>
</template>
<script setup>
function handleClick(e) {
console.log(e) // 原生 Event 对象
}
</script>
2. 需要自定义参数 + 事件对象
只要传了自定义参数,就必须手动把 $event 一起传进去,否则事件对象会被覆盖掉:
xml
<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. 获取当前点击的元素
xml
<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. 输入框的输入事件
xml
<template>
<input @input="handleInput" />
</template>
<script setup>
function handleInput(e) {
console.log(e.target.value) // 当前输入的值
}
</script>
3. 键盘事件(回车、按键判断)
xml
<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. 阻止冒泡和默认行为
优先用修饰符,简单清晰:
xml
<!-- 阻止冒泡 -->
<div @click.stop="handleClick">...</div>
<!-- 阻止默认行为(如表单提交、a 标签跳转) -->
<form @submit.prevent="handleSubmit">...</form>
<!-- 组合 -->
<a href="..." @click.prevent.stop="handleClick">链接</a>
需要在函数里动态判断时,再用事件对象方法:
scss
function handleClick(e) {
if (某种条件) {
e.stopPropagation() // 阻止冒泡
}
if (另一种条件) {
e.preventDefault() // 阻止默认行为
}
}
三、开发高频坑点汇总:
坑点 1:传了自定义参数却忘了 $event
xml
<template>
<!-- 错误 -->
<button @click="handleClick(10)">点击</button>
</template>
<script setup>
// 你以为 e 是事件对象,其实是 10
function handleClick(e) {
console.log(e.target) // undefined,报错!
}
</script>
解决:需要事件对象就手动传 $event 。
坑点 2:参数顺序写反
xml
<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:修饰符和函数内阻止逻辑重复
xml
<template>
<!-- 用了 .stop -->
<div @click.stop="handleClick">...</div>
</template>
<script setup>
function handleClick(e) {
e.stopPropagation() // 又手动阻止一次
}
</script>
重复阻止一般不会报错,但会让逻辑混乱、排查困难。建议统一规范:
- 简单、固定的场景:用模板修饰符
- 需要条件判断的动态场景:只用函数内方法,别混用
坑点 5:异步回调里直接使用事件对象
javascript
function handleClick(e) {
setTimeout(() => {
console.log(e.target) // 可能拿到 null / undefined
}, 500)
}
原生事件对象在事件处理完后会被复用/回收,异步里再访问内容不稳定。
解决:先缓存需要的值。
javascript
function handleClick(e) {
const el = e.target
const val = e.target.value
setTimeout(() => {
console.log(el, val) // 用缓存值,安全
}, 500)
}
坑点 6:键盘事件用 keyCode 已废弃
ini
// 老写法,已废弃
if (e.keyCode === 13) { ... }
用现代写法:
ini
if (e.key === 'Enter') { ... }
if (e.code === 'Enter') { ... }
- e.key:表示按键的字符 / 名称(如 'Enter'、'a'、'ArrowUp')
- e.code:表示物理按键位置(如 'KeyA'、'ArrowUp')
区分中英文输入法布局时,用 e.code 更可靠。
坑点 7:事件绑定到组件标签上(组件事件)
给自定义组件用 @click 时,默认监听的是组件自定义事件,不是原生 DOM 事件:
xml
<!-- 这样写,默认不会触发原生 click -->
<MyButton @click="handleClick" />
如果想让事件落到底层原生元素上,需要:
arduino
<MyButton @click="handleClick" @click.capture="..." />
或者在组件内部显式 defineEmits:
scss
// MyButton.vue
const emit = defineEmits(['click'])
function onClick(e) {
emit('click', e)
}
简单记:原生 DOM 元素直接用 @click 没问题;自定义组件上想监听原生事件,要么组件内部 emit ,要么用 .capture 等原生修饰符。
四、事件常用修饰符一览:

常用按键快捷写法:
ini
<input @keyup.enter="submit" />
<input @keyup.esc="cancel" />
<input @keyup.ctrl.enter="save" />
五、事件处理函数几种写法的取舍:

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