修饰符

修饰符是 Vue 提供的指令语法糖,用来声明事件、表单绑定或按键行为的附加规则。

一、事件修饰符

事件修饰符写在事件名后面:

<button @click.stop="handleClick">提交</button>
<form @submit.prevent="handleSubmit">...</form>
修饰符作用原理
.stop阻止事件继续冒泡调用 event.stopPropagation()
.prevent阻止浏览器默认行为调用 event.preventDefault()
.capture在捕获阶段触发监听器注册 capture: true 的监听器
.self只有事件目标是当前元素时才处理判断 event.target === event.currentTarget
.once最多触发一次注册 once: true 的监听器
.passive声明监听器不会调用 preventDefault()注册 passive: true 的监听器

.self 和冒泡

<!-- 点击 div 自身才触发;点击 button 不触发此处理器 -->
<div @click.self="handleClick">
  <button>按钮</button>
</div>

.self 判断的是事件的原始目标 target,不是当前正在执行处理器的元素 currentTarget。因此,子元素冒泡上来的事件不会通过 .self

修饰符顺序

修饰符会按书写顺序生成处理逻辑,因此顺序可能影响结果:

@click.prevent.self="handleClick"
@click.self.prevent="handleClick"

可以近似理解为:

// .prevent.self:先阻止默认行为,再判断是否为元素自身
event.preventDefault()
if (event.target !== event.currentTarget) return
handleClick(event)

// .self.prevent:先判断目标,再阻止默认行为
if (event.target !== event.currentTarget) return
event.preventDefault()
handleClick(event)

所以,前者会阻止子元素点击的默认行为,后者只会阻止元素自身点击的默认行为。

.passive.prevent 不应同时使用。前者明确声明不会阻止默认行为,浏览器可能忽略后者并输出警告。

二、按键修饰符

按键修饰符用于键盘事件:

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

常用按键修饰符:

  • .enter.tab.delete.esc.space.up.down.left.right
  • .ctrl.alt.shift.meta:系统修饰键
  • .exact:要求系统修饰键组合完全匹配
<!-- 只允许 Ctrl + Enter;Ctrl + Shift + Enter 不触发 -->
<input @keyup.ctrl.enter.exact="submit" />

按键修饰符本质上是对 KeyboardEvent 的条件判断:Vue 会检查 event.key(或对应按键别名)以及 ctrlKeyaltKeyshiftKeymetaKey 等属性,条件满足后才调用处理函数。

三、表单修饰符

<input v-model.trim="name" />
<input v-model.number="age" />
<input v-model.lazy="keyword" />
修饰符作用说明
.trim去除字符串首尾空白写入绑定值前执行 trim()
.number尝试转换为数字通常使用 parseFloat;转换失败时保留原值
.lazy改用 change 事件同步不再随每次 input 更新,通常在失焦或提交变更时更新

.lazy 是事件时机切换,不是防抖,也不会延迟一段固定时间后再执行。

多个修饰符可以组合:

<input v-model.trim.number="price" />

四、自定义v-model 修饰符(Vue 3)

组件上的修饰符不会自动改变值,Vue 会把它们作为 prop 传给组件,由组件决定如何处理:

<!-- Parent.vue -->
<Input v-model.capitalize="text" />

对于默认的 v-model,子组件接收 modelModifiers

<!-- Input.vue -->
<script setup>
const props = defineProps({
  modelValue: String,
  modelModifiers: {
    default: () => ({})
  }
})

const emit = defineEmits(['update:modelValue'])

function handleInput(event) {
  let value = event.target.value

  if (props.modelModifiers.capitalize) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }

  emit('update:modelValue', value)
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>

带参数的 v-model 使用对应名称的修饰符 prop:

<UserName v-model:first.capitalize="firstName" />

组件中接收 firstModifiers,而不是 modelModifiers

五、修饰符的实现原理

修饰符主要在模板编译阶段被转换成普通 JavaScript 逻辑或原生监听器配置。以事件绑定为例:

<button @click.stop.prevent="save">保存</button>

可以近似看成:

button.addEventListener('click', (event) => {
  event.stopPropagation()
  event.preventDefault()
  save(event)
})

.capture.once.passive 等会进一步影响 addEventListener 的 options:

button.addEventListener('click', handler, {
  capture: true,
  once: true,
  passive: true
})

v-model 修饰符则会影响“读取事件值”和“写回响应式数据”的过程:

用户输入
读取 input/change 事件的 value
trim / number 等转换
更新绑定的 ref 或 reactive 属性

因此,修饰符不是浏览器 API,也不是独立的运行时魔法,而是 Vue 在编译或运行时帮助我们补上的条件判断、事件调用和监听器配置。

六、常见误区

  • .stop 只影响事件传播,不会阻止链接跳转或表单提交;需要阻止默认行为时使用 .prevent
  • .prevent 只对有默认行为的事件有意义,不能代替 .stop
  • .self 不等于“只监听当前元素注册的事件”,它只检查 event.target 是否就是当前元素。
  • .once 是监听器最多执行一次,不是绑定的数据只更新一次。
  • .passive.prevent 语义冲突,不要组合使用。
  • .lazy 不是防抖;需要防抖时应使用防抖函数或专门的 composable。