事件修饰符

在处理 DOM 事件时,我们经常需要做一些"额外操作":阻止表单默认提交、阻止事件冒泡、只在特定阶段触发...传统做法是在事件处理函数中调用 event.preventDefault()event.stopPropagation()

Vue 提供了事件修饰符,让这些操作变得更优雅。通过在事件名后添加点号后缀,一行代码就能搞定这些常见需求。

为什么需要修饰符

看一个常见的场景:

<form @click="submitForm">
  <button type="submit">提交</button>
</form>

点击按钮时,表单会默认提交并刷新页面。传统做法:

methods: {
  submitForm: function(event) {
    event.preventDefault()  // 阻止默认行为
    // 提交逻辑...
  }
}

使用修饰符后:

<form @click.prevent="submitForm">
  <button type="submit">提交</button>
</form>

逻辑更清晰,模板中就能看出"这个事件会阻止默认行为"。

修饰符列表

Vue 提供了以下事件修饰符:

修饰符作用原生等价写法
.stop阻止事件冒泡event.stopPropagation()
.prevent阻止默认行为event.preventDefault()
.capture使用事件捕获模式addEventListener(type, handler, true)
.self只当事件在该元素本身触发时触发判断 event.target
.once事件只触发一次手动移除监听器
.passive提升移动端滚动性能{ passive: true }

详细用法

.stop - 阻止冒泡

事件冒泡是 DOM 的默认行为:子元素的事件会向上传递给父元素。

<div id="app">
  <div class="outer" @click="handleOuter">
    外层 DIV
    <div class="inner" @click="handleInner">
      内层 DIV
    </div>
  </div>
</div>

<script>
new Vue({
  el: '#app',
  methods: {
    handleOuter: function() {
      console.log('外层被点击')
    },
    handleInner: function() {
      console.log('内层被点击')
    }
  }
})
</script>

点击内层 DIV,控制台输出:

内层被点击
外层被点击  // 冒泡触发

使用 .stop 阻止冒泡:

<div class="inner" @click.stop="handleInner">
  内层 DIV
</div>

现在点击内层,只会输出"内层被点击"。

实际应用:弹窗点击外部关闭,但点击内部不关闭。

<div class="modal-overlay" @click="closeModal">
  <div class="modal-content" @click.stop>
    <!-- 点击这里不会关闭弹窗 -->
    <h2>弹窗标题</h2>
    <p>弹窗内容...</p>
  </div>
</div>

.prevent - 阻止默认行为

最常见的场景是阻止表单提交和链接跳转:

<!-- 阻止表单默认提交 -->
<form @submit.prevent="handleSubmit">
  <input type="text" v-model="name">
  <button type="submit">提交</button>
</form>

<!-- 阻止链接跳转 -->
<a href="https://example.com" @click.prevent="handleClick">
  点击不跳转
</a>

实际应用:自定义链接行为。

<a href="/detail/123" @click.prevent="goToDetail(123)">
  查看详情
</a>

<script>
methods: {
  goToDetail: function(id) {
    // 使用路由跳转,而不是默认的链接跳转
    this.$router.push('/detail/' + id)
  }
}
</script>

.capture - 事件捕获

默认情况下,事件在冒泡阶段触发。使用 .capture 可以在捕获阶段触发。

<div class="outer" @click.capture="handleOuter">
  外层
  <div class="inner" @click="handleInner">
    内层
  </div>
</div>

点击内层,输出顺序:

外层被点击  // 捕获阶段先触发
内层被点击  // 冒泡阶段后触发

实际应用:全局事件拦截。

<div class="app" @click.capture="checkPermission">
  <button @click="deleteItem">删除</button>
</div>

<script>
methods: {
  checkPermission: function(event) {
    if (!this.hasPermission) {
      event.stopPropagation()  // 没权限就拦截事件
      alert('没有操作权限')
    }
  },
  deleteItem: function() {
    // 有权限才会执行
    console.log('删除成功')
  }
}
</script>

.self - 只在自身触发

.self 修饰符确保事件只在元素本身触发,不包括子元素。

<div class="box" @click.self="handleClick">
  <button>按钮</button>
</div>
  • 点击按钮:不触发 handleClick
  • 点击 box 的空白区域:触发 handleClick

实际应用:点击遮罩关闭弹窗,但点击内容区域不关闭。

<div class="overlay" @click.self="close">
  <div class="content">
    弹窗内容
    <button>操作按钮</button>
  </div>
</div>

.once - 只触发一次

事件处理器只执行一次,之后自动移除。

<button @click.once="showWelcome">
  点击显示欢迎信息(只显示一次)
</button>

<script>
methods: {
  showWelcome: function() {
    alert('欢迎!')
    // 第二次点击不会有任何反应
  }
}
</script>

实际应用:首次点击引导、一次性操作。

<div class="guide-tip" @click.once="hideGuide">
  点击关闭引导提示(永久关闭)
</div>

.passive - 提升滚动性能

.passive 主要用于提升移动端的滚动性能,告诉浏览器不会调用 preventDefault()

<div @scroll.passive="onScroll">
  很长的内容...
</div>

浏览器在等待 touchstarttouchmove 事件处理结果时,会延迟滚动。使用 .passive 后,浏览器知道你不会阻止默认行为,可以立即开始滚动。

适用场景:滚动监听、触摸事件。

<div class="scroll-container" @touchmove.passive="onTouchMove">
  滚动内容
</div>

修饰符组合

修饰符可以串联使用,执行顺序从左到右:

<!-- 阻止冒泡 + 阻止默认行为 -->
<button @click.stop.prevent="doSomething">按钮</button>

<!-- 捕获模式 + 只触发一次 -->
<div @click.capture.once="init">初始化</div>

<!-- 自身触发 + 阻止冒泡 -->
<div @click.self.stop="handleClick">内容</div>

注意顺序

修饰符的顺序会影响执行结果:

@click.prevent.stop  // 先阻止默认行为,再阻止冒泡
@click.stop.prevent  // 先阻止冒泡,再阻止默认行为

虽然大多数情况下结果相同,但某些场景下顺序很重要。

实战示例

阻止右键菜单

<div @contextmenu.prevent="showCustomMenu">
  右键点击显示自定义菜单
</div>

<script>
methods: {
  showCustomMenu: function(event) {
    // 显示自定义右键菜单
    this.menuVisible = true
    this.menuX = event.clientX
    this.menuY = event.clientY
  }
}
</script>

表单验证后提交

<form @submit.prevent="validateAndSubmit">
  <input type="text" v-model="email" placeholder="邮箱">
  <input type="password" v-model="password" placeholder="密码">
  <button type="submit">注册</button>
</form>

<script>
methods: {
  validateAndSubmit: function() {
    if (!this.validateEmail(this.email)) {
      alert('邮箱格式不正确')
      return
    }
    if (this.password.length < 6) {
      alert('密码至少6位')
      return
    }
    // 验证通过,提交表单
    this.submitForm()
  }
}
</script>

下拉菜单点击外部关闭

<div class="dropdown" @click.self="closeDropdown">
  <button @click.stop="toggleDropdown">
    {{ isOpen ? '收起' : '展开' }}
  </button>
  <div class="menu" v-show="isOpen">
    <a href="#" @click.prevent="selectItem(1)">选项一</a>
    <a href="#" @click.prevent="selectItem(2)">选项二</a>
    <a href="#" @click.prevent="selectItem(3)">选项三</a>
  </div>
</div>

按键修饰符

除了事件修饰符,Vue 还提供了按键修饰符,用于监听特定按键。这部分内容将在下一节详细介绍。

小结

事件修饰符让代码更简洁、意图更明确:

修饰符使用场景
.stop阻止事件冒泡,弹窗内部点击不关闭
.prevent阻止默认行为,表单提交、链接跳转
.capture事件捕获阶段触发,全局拦截
.self只在元素自身触发,排除子元素
.once只触发一次,引导提示、初始化
.passive提升滚动性能,移动端优化

合理使用修饰符,能让代码更易读、更易维护。但也要避免过度使用,保持代码的简洁性。