Vue 事件处理
前端交互的核心就是"事件":用户点击按钮、输入文字、按下回车——程序要响应这些动作。Vue 用 @(原 v-on)把事件绑定到方法,还提供了大量修饰符让你写更声明式、更好读的事件代码。本章专门展开。
1. 基本用法
@click 后面跟的可以是函数名(不带括号),也可以是内联表达式(带括号):
<template>
<!-- 1. 方法处理器:直接写函数名,不带括号 -->
<button @click="onClick">点我</button>
<!-- 2. 内联表达式:带括号,可传参 -->
<button @click="say('hi')">打招呼</button>
<!-- 3. 内联表达式 + $event -->
<button @click="say('hi', $event)">打招呼</button>
<!-- 4. 不带括号的方法处理器:Vue 自动注入 event -->
<button @click="onClick">按钮</button>
</template>
<script setup>
function onClick(event) {
// 不带括号调用时,event 是默认参数
console.log(event.target);
}
function say(msg, event) {
console.log(msg, event.target);
}
</script>两种写法的区别:
- 方法处理器(不带括号):
@click="onClick",Vue 自动把 event 对象作为参数传给 onClick。 - 内联表达式(带括号):
@click="say('hi')",需要手动传 event 时用$event。
实际上写多了你会发现:大部分时候需要传业务参数,所以内联表达式更常用,通过 $event 拿到原生事件对象。
2. 事件修饰符
Vue 把常见的"事件处理套路"做成了修饰符,让你不用在回调里手动操作 event。比如阻止默认行为、阻止冒泡——直接写在模板上,意图一目了然。
<template>
<!-- .stop:阻止事件冒泡(等价 e.stopPropagation()) -->
<div @click="outerClick">
<button @click.stop="innerClick">只触发自己</button>
</div>
<!-- .prevent:阻止默认行为(等价 e.preventDefault()) -->
<form @submit.prevent="onSubmit">
<button type="submit">提交</button>
</form>
<!-- .self:仅当事件目标是元素本身时才触发 -->
<div @click.self="onSelf">
<p>点这里不会触发外层 onSelf</p>
</div>
<!-- .capture:在捕获阶段处理(默认是冒泡阶段) -->
<div @click.capture="onCapture">...</div>
<!-- .once:只触发一次 -->
<button @click.once="onFirstClick">第一次点击有效</button>
<!-- .passive:提升滚动性能(告知浏览器不调 preventDefault) -->
<div @scroll.passive="onScroll">...</div>
<!-- 链式:多个修饰符一起用 -->
<button @click.stop.prevent="onSave">保存</button>
</template>.stop:阻止事件冒泡(子元素点击不会触发父元素的 click)。.prevent:阻止默认行为(表单提交不刷新页面、a 标签不跳转)。.capture:在事件捕获阶段(从外到内)处理,默认是冒泡阶段(从内到外)。.self:仅当事件目标就是元素本身(不是子元素)时才触发。.once:事件只触发一次,之后自动解绑。.passive:告诉浏览器"我不会调 preventDefault",提升滚动性能(移动端常用)。
修饰符可以链式使用:@click.stop.prevent 表示既阻止冒泡又阻止默认。顺序不同效果可能不同(从左到右执行)。
3. 按键修饰符
处理键盘事件时,你常需要"按特定键才触发"。Vue 把所有键名做成了修饰符:
<template>
<!-- 按键修饰符:常用键 -->
<input @keyup.enter="onSearch" placeholder="回车搜索" />
<input @keyup.esc="onCancel" placeholder="Esc 取消" />
<input @keyup.delete="onDelete" placeholder="Backspace 删除" />
<!-- 系统修饰符:组合键 -->
<button @click.ctrl="onCtrlClick">Ctrl + 点击</button>
<button @click.meta="onMacCmd">Mac:Cmd + 点击</button>
<input @keyup.alt.enter="onAltEnter" placeholder="Alt + Enter" />
<!-- .exact 修饰符:精确控制组合键 -->
<!-- 仅 Ctrl(不能同时按 Shift/Alt) -->
<button @click.ctrl.exact="onPureCtrl">仅 Ctrl</button>
<!-- 鼠标修饰符 -->
<div @click.left="onLeft" @click.right="onRight" @click.middle="onMiddle">
左/右/中键点击
</div>
</template>常用按键:
.enter.tab.delete(Backspace 和 Delete 都算).esc.space.up.down.left.right。- 系统修饰键:
.ctrl.alt.shift.meta(Mac 上是 Command,Windows 上是 Win 键)。 .exact:精确匹配修饰键。比如@click.ctrl.exact表示"必须按 Ctrl,且不能同时按其他修饰键"。
一个超实用场景:输入框里回车搜索。<input @keyup.enter="onSearch">,一行搞定。老写法要在 JS 里 if (e.key === 'Enter'),啰嗦得多。
4. 鼠标修饰符
区分鼠标按键:.left(左键).right(右键).middle(中键)。比如禁用右键菜单:
<div @contextmenu.prevent>:阻止默认的右键菜单。<div @click.right="onRightClick">:仅右键时触发回调。
5. 自定义组件事件
除了原生 DOM 事件,Vue 还能让自定义组件触发事件。这是父子组件通信的核心机制:
<!-- 子组件 Child.vue -->
<template>
<button @click="emitAdd">+1</button>
</template>
<script setup>
// 定义可以向父组件触发的事件
const emit = defineEmits(["add", "reset"]);
function emitAdd() {
// 触发 add 事件,可携带数据
emit("add", 1);
}
</script>
<!-- 父组件 Parent.vue -->
<template>
<!-- 监听子组件触发的 add 事件 -->
<Child @add="onChildAdd" @reset="onReset" />
<p>{{ count }}</p>
</template>
<script setup>
import { ref } from "vue";
import Child from "./Child.vue";
const count = ref(0);
function onChildAdd(num) {
count.value += num; // 接收子组件传过来的数据
}
function onReset() {
count.value = 0;
}
</script>要点:
- 子组件用
defineEmits声明它能触发的事件(类型检查更严)。 - 触发用
emit('事件名', 数据),数据可以是任何类型。 - 父组件用
@事件名监听,和原生事件完全一样的语法。
这一机制我们会在《Props 与 Emit》一章更详细地展开,这里先建立"组件也能触发事件"的印象。
6. 在 v-for 里传索引
列表里每个按钮都要知道"自己是第几项",用内联表达式传索引:
<template>
<ul>
<!-- 传索引 -->
<li v-for="(item, i) in list" :key="item.id">
<button @click="remove(i)">删除 {{ item.name }}</button>
</li>
</ul>
</template>
<script setup>
import { ref } from "vue";
const list = ref([
{ id: 1, name: "苹果" },
{ id: 2, name: "香蕉" },
]);
function remove(index) {
list.value.splice(index, 1);
}
</script>这是事件处理的高频场景:删除、编辑、查看详情,都需要知道操作的是哪一项。把 index 或 item.id 传给方法即可。
7. 事件名规范
Vue 3 推荐事件名用短横线(kebab-case):emit('item-click', data) 而不是 emit('itemClick', data)。因为模板里 @item-click 会被自动转成小写事件名匹配,而驼峰会出问题。养成习惯即可。
8. 实战:点击外部关闭弹窗
一个常见交互:点击弹窗外面时关闭它。Vue 怎么实现?在 body 上监听 click,弹窗内部用 @click.stop 阻止冒泡:
<template>
<div class="modal-mask" @click="closeMask" v-if="visible">
<!-- 点击内部阻止冒泡到 mask,不触发 close -->
<div class="modal-body" @click.stop>
<h3>弹窗</h3>
<button @click="visible = false">关闭</button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const visible = ref(true);
function closeMask() {
visible.value = false;
}
</script>这种"用 .stop 隔离冒泡"的写法,比手动监听 document 的 click 然后判断 target 简洁得多。修饰符的威力就在于此——把复杂逻辑压缩成几个字符。
小结
这一章你掌握了 Vue 事件处理的全部要点:基本绑定、$event、6 个事件修饰符、按键/鼠标修饰符、自定义组件事件。下一篇我们看表单——v-model 让表单双向绑定变得无比简单。
← 上一篇 Vue 内置指令
下一篇 Vue 表单与 v-model →