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>

两种写法的区别:

实际上写多了你会发现:大部分时候需要传业务参数,所以内联表达式更常用,通过 $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>

修饰符可以链式使用:@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>

常用按键:

一个超实用场景:输入框里回车搜索。<input @keyup.enter="onSearch">,一行搞定。老写法要在 JS 里 if (e.key === 'Enter'),啰嗦得多。

4. 鼠标修饰符

区分鼠标按键:.left(左键).right(右键).middle(中键)。比如禁用右键菜单:

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>

要点:

这一机制我们会在《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

✈️💬