Vue 3 事件处理完全指南
本文档从零开始讲解 Vue 3 的事件处理:如何绑定点击、输入、提交等事件,如何传参、如何拿到原生事件对象,以及事件修饰符、按键修饰符、系统修饰符 等,配有大量示例,适合新手系统学习。
一、什么是“事件处理”?
1.1 生活中的类比
页面上用户的操作(点击按钮、输入文字、按下回车、提交表单)都会触发浏览器的事件。
我们要在代码里“监听”这些事件,并执行自己的逻辑(例如发请求、改数据、弹窗)。
在 Vue 里,用 v-on 或简写 @ 把“事件名”和“要执行的代码”绑在一起,这就是事件处理。
1.2 基本写法
语法: v-on:事件名="处理方式" 或 @事件名="处理方式"
- 事件名:如
click、input、submit、keyup等,和原生 DOM 事件名一致。 - 处理方式:可以是一句内联表达式、一个方法名、或调用方法并传参。
<template>
<button @click="count++">点击了 {{ count }} 次</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
- @click 表示“监听点击事件”。
- “count++” 是内联表达式,点击一次就执行一次,count 加 1,页面自动更新。
下面从“内联 vs 方法”“传参”“事件对象”开始,再讲修饰符。
二、内联处理 vs 方法处理
2.1 内联处理器(直接写表达式)
在模板里直接写一句或几句表达式,用逗号或分号分隔(一般不写多句,可读性差):
<template>
<div>
<button @click="count++">+1</button>
<button @click="count--">-1</button>
<button @click="visible = !visible">切换显示</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const visible = ref(true)
</script>
适合非常简单的逻辑;逻辑一多就建议改成方法。
2.2 方法处理器(写方法名)
把逻辑写在 script 里的函数中,模板里只写函数名(不写括号表示“引用这个函数”,点击时由 Vue 调用):
<template>
<button @click="handleClick">点击</button>
</template>
<script setup>
function handleClick() {
console.log('被点了')
alert('你好')
}
</script>
- @click=”handleClick”:点击时调用
handleClick(),不传参。 - 若写成 @click=”handleClick()”,也会在点击时执行,但含义是“执行 handleClick 的返回值”;若返回值不是函数,一般不这样写。传参时必须写括号,见下一小节。
2.3 调用方法并传参
需要传参时,必须写括号,在括号里写参数;需要事件对象时,用 $event 传进去:
<template>
<div>
<button @click="say('你好')">打招呼</button>
<button @click="add(1, 2)">1+2</button>
<button @click="handleWithEvent($event)">需要事件对象</button>
</div>
</template>
<script setup>
function say(msg) {
alert(msg)
}
function add(a, b) {
alert(a + b)
}
function handleWithEvent(e) {
console.log(e.target)
}
</script>
- $event 是 Vue 提供的占位变量,表示“当前这个 DOM 事件的 event 对象”,只在事件绑定里有效。
- 若既要传参又要事件对象,可写:
@click="handle(id, $event)",在方法里用第二个参数接收e。
三、事件对象($event)详解
3.1 什么时候需要事件对象?
例如:
- 知道被点击的是哪个元素(e.target)
- 阻止默认行为(如阻止表单提交、链接跳转)
- 阻止冒泡(e.stopPropagation)
- 拿到键盘事件的键码(e.key、e.keyCode)
在 @事件名 的表达式里,用 $event 代表“当前这次事件的 event 对象”,传给方法即可。
3.2 只传事件对象
<template>
<button @click="onClick($event)">点击</button>
</template>
<script setup>
function onClick(e) {
console.log(e.target)
console.log(e.type)
}
</script>
3.3 既传业务参数又传事件对象
<template>
<button @click="onDelete(item.id, $event)">删除</button>
</template>
<script setup>
function onDelete(id, e) {
if (confirm('确定删除?')) {
console.log('删除 id:', id)
}
e.preventDefault()
}
</script>
四、事件修饰符(常用)
在事件名后加 点 + 修饰符名,可以省去在方法里手写 e.preventDefault()、e.stopPropagation() 等,让模板更简洁。
4.1 .prevent —— 阻止默认行为
等价于在方法里写 e.preventDefault()。
常用在:表单提交(不刷新整页)、链接点击(不跳转)。
<template>
<form @submit.prevent="onSubmit">
<input type="text" />
<button type="submit">提交</button>
</form>
<a href="https://vuejs.org" @click.prevent="go">去 Vue 官网</a>
</template>
<script setup>
function onSubmit() {
console.log('表单提交逻辑,页面不会刷新')
}
function go() {
console.log('不会跳转,可在这里做路由跳转等')
}
</script>
4.2 .stop —— 阻止冒泡
等价于 e.stopPropagation()。
子元素点击时,事件不会继续“冒泡”到父元素,父元素上的点击监听不会触发。
<template>
<div @click="onDivClick">
<button @click.stop="onBtnClick">点我</button>
</div>
</template>
<script setup>
function onDivClick() {
console.log('div 被点了')
}
function onBtnClick() {
console.log('只有按钮被点了,div 不会触发')
}
</script>
不加 .stop 时,点按钮会先打印“只有按钮…”,再打印“div 被点了”;加了 .stop 后只打印“只有按钮…”。
4.3 .once —— 最多触发一次
该事件只会触发一次,之后自动“解绑”,不再触发。
<template>
<button @click.once="doOnce">只执行一次</button>
</template>
<script setup>
function doOnce() {
console.log('只会出现一次')
}
</script>
4.4 .capture —— 使用捕获阶段
默认是冒泡阶段触发;加上 .capture 后,在捕获阶段就触发(从外到内)。
一般用得少,了解即可。
<div @click.capture="onCapture">...</div>
4.5 .self —— 仅当事件来自当前元素自身时才触发
只有 e.target 是当前元素自己时才执行处理函数;子元素触发的冒泡上来的事件不会触发当前元素。
<template>
<div class="outer" @click.self="onSelf">
<button>点我</button>
</div>
</template>
<script setup>
function onSelf() {
console.log('只有直接点击 div 区域(且不是按钮)才触发')
}
</script>
4.6 修饰符可以链式写
多个修饰符可以连写,顺序有时会影响结果(例如 .prevent 和 .stop 一般写在前):
<form @submit.prevent.stop="onSubmit">...</form>
<button @click.stop.prevent="onClick">...</button>
4.7 事件修饰符小结
| 修饰符 | 作用 |
|---|---|
| .prevent | 调用 e.preventDefault(),阻止默认行为 |
| .stop | 调用 e.stopPropagation(),阻止冒泡 |
| .once | 事件最多触发一次 |
| .capture | 使用捕获阶段 |
| .self | 仅当 e.target 是当前元素时触发 |
| .passive | 表示不调用 preventDefault,适合滚动性能优化(与 .prevent 不要同时用) |
五、按键修饰符
监听键盘事件(如 keyup、keydown)时,经常只关心“按了哪个键”。
可以用 按键修饰符 限定:只有按下指定键时才触发。
5.1 常用按键别名
在 @keyup 或 @keydown 后加 .键名:
<template>
<input
type="text"
@keyup.enter="onEnter"
@keyup.esc="onEsc"
@keyup.tab="onTab"
/>
</template>
<script setup>
function onEnter() {
console.log('按了回车')
}
function onEsc() {
console.log('按了 Esc')
}
function onTab() {
console.log('按了 Tab')
}
</script>
常见别名:
enter、tab、delete(含 Backspace 和 Delete)、esc、space、up、down、left、right。
5.2 多个键组合(系统修饰符)
只有同时按下 修饰键(如 Ctrl、Alt、Shift、Meta)和普通键时才触发,可以用 .键名.修饰键:
<template>
<input @keyup.ctrl.enter="onCtrlEnter" />
</template>
<script setup>
function onCtrlEnter() {
console.log('Ctrl + Enter')
}
</script>
系统修饰符: .ctrl、.alt、.shift、.meta(Windows 下 meta 多为 Win 键)。
5.3 .exact —— 精确匹配修饰键
.exact 表示“只有这些修饰键被按下,没有其它修饰键”时才触发:
<button @click.ctrl="onClick">Ctrl + 点击</button>
<button @click.ctrl.exact="onCtrlOnly">仅 Ctrl + 点击</button>
- 第一个:Ctrl+点击 会触发;Ctrl+Shift+点击 也会触发。
- 第二个:只有“仅 Ctrl + 点击”才触发,Ctrl+Shift+点击 不触发。
5.4 使用 key 字符串(自定义键)
若按键没有内置别名,可以用 key 的字符串(和 e.key 一致):
<input @keyup.page-down="onPageDown" />
六、鼠标按键修饰符
只在意“用鼠标哪个键点击”时,可加:
.left、.right、.middle(左键、右键、中键)。
<button @click.left="onLeft">左键</button>
<button @click.right="onRight">右键</button>
七、在组件上使用 v-on(自定义事件)
在自定义组件上写的 @事件名,监听的是该组件 emit 出来的自定义事件,不是原生 DOM 事件。
若要监听组件根元素上的原生事件,需要子组件用 emits 暴露并再 emit,或在父组件用 .native(Vue 3 已移除 .native,改用 emits 或 $attrs)。
<template>
<MyButton @click="onClick" />
</template>
这里的 click 若子组件通过 emit(‘click’) 触发,则 onClick 会执行;若子组件没有 emit(‘click’),则不会执行。
组件事件处理的详细用法见《Vue3组件.md》。
八、同时绑定多个处理函数
可以在表达式里写多个调用,用逗号分隔;或在一个方法里再调用其它方法:
<template>
<button @click="fn1(); fn2()">多函数</button>
<button @click="handleAll">一个方法里调多个</button>
</template>
<script setup>
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
function handleAll() {
fn1()
fn2()
}
</script>
实际开发中更推荐 一个方法 handleAll,逻辑更清晰。
九、常见场景示例
9.1 表单提交(阻止默认提交)
<template>
<form @submit.prevent="onSubmit">
<input v-model="name" placeholder="姓名" />
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const name = ref('')
function onSubmit() {
console.log('提交:', name.value)
}
</script>
9.2 输入框回车搜索
<template>
<input
v-model="keyword"
type="text"
placeholder="输入后按回车搜索"
@keyup.enter="search"
/>
</template>
<script setup>
import { ref } from 'vue'
const keyword = ref('')
function search() {
console.log('搜索:', keyword.value)
}
</script>
9.3 列表项点击并拿到数据
<template>
<ul>
<li
v-for="item in list"
:key="item.id"
@click="selectItem(item)"
>
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, name: '选项一' },
{ id: 2, name: '选项二' }
])
function selectItem(item) {
console.log('选中', item)
}
</script>
9.4 阻止链接跳转并做路由
<template>
<a href="/detail" @click.prevent="goDetail">查看详情</a>
</template>
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function goDetail() {
router.push('/detail')
}
</script>
9.5 点击遮罩关闭弹窗(.self 示例)
<template>
<div class="mask" @click.self="close">
<div class="modal" @click.stop>
弹窗内容
<button @click="close">关闭</button>
</div>
</div>
</template>
<script setup>
const emit = defineEmits(['close'])
function close() {
emit('close')
}
</script>
- 点遮罩(mask)会触发 close(.self 保证只有直接点 mask 才触发)。
- 点弹窗内部有 @click.stop,不会冒泡到 mask,不会误关。
- 点“关闭”按钮也会执行 close。
十、易错点与注意点
10.1 方法名要不要括号
- @click=”fn”:传的是函数引用,点击时由 Vue 调用,无参时常用。
- @click=”fn()”:每次求值都会执行 fn();要传参或传 $event 时必须写括号,如 @click=”fn(1, $event)”。
10.2 $event 只在事件绑定里有效
$event 是模板里事件绑定表达式的“占位变量”,只在 v-on 的赋值里有效,在 {{ }} 或其它地方不能直接用。
10.3 修饰符顺序
多个修饰符连写时,顺序可能影响行为。常见写法:.stop、.prevent 放前面,.once、.self 等放后面;.capture 和 .passive 按需求写。
.prevent 和 .passive 不要同时用(语义冲突)。
10.4 逻辑不要堆在模板里
内联表达式适合 1~2 句简单逻辑;稍复杂就抽成方法,模板里只写 @事件=”方法名” 或 @事件=”方法(参数)”,便于维护和测试。
十一、事件处理速查表
| 需求 | 写法 |
|---|---|
| 点击 | @click=”fn” 或 @click=”fn()” |
| 传参 | @click=”fn(1, 2)” |
| 需要事件对象 | @click=”fn($event)” 或 @click=”fn(id, $event)” |
| 阻止默认行为 | @submit.prevent 或 @click.prevent |
| 阻止冒泡 | @click.stop |
| 只触发一次 | @click.once |
| 回车键 | @keyup.enter |
| Esc | @keyup.esc |
| Ctrl+点击 | @click.ctrl |
| 精确修饰键 | @click.ctrl.exact |
十二、学习建议
- 先练熟 @click 的两种写法:无参用方法名,有参用 fn(参数),需要事件对象用 $event。
- 记住常用事件修饰符:.prevent(表单/链接)、.stop(冒泡)、.once(只触发一次)。
- 表单和输入框常用 @submit.prevent、@keyup.enter。
- 逻辑稍多就抽成方法,保持模板简洁。
把本文档里的示例在项目里敲一遍、改一改,会掌握得更牢。组件自定义事件见《Vue3组件.md》。祝你学习顺利。