在处理 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 } |
事件冒泡是 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>
最常见的场景是阻止表单提交和链接跳转:
<!-- 阻止表单默认提交 -->
<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 可以在捕获阶段触发。
<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 修饰符确保事件只在元素本身触发,不包括子元素。
<div class="box" @click.self="handleClick">
<button>按钮</button>
</div>
handleClickhandleClick实际应用:点击遮罩关闭弹窗,但点击内容区域不关闭。
<div class="overlay" @click.self="close">
<div class="content">
弹窗内容
<button>操作按钮</button>
</div>
</div>
事件处理器只执行一次,之后自动移除。
<button @click.once="showWelcome">
点击显示欢迎信息(只显示一次)
</button>
<script>
methods: {
showWelcome: function() {
alert('欢迎!')
// 第二次点击不会有任何反应
}
}
</script>
实际应用:首次点击引导、一次性操作。
<div class="guide-tip" @click.once="hideGuide">
点击关闭引导提示(永久关闭)
</div>
.passive 主要用于提升移动端的滚动性能,告诉浏览器不会调用 preventDefault()。
<div @scroll.passive="onScroll">
很长的内容...
</div>
浏览器在等待 touchstart 和 touchmove 事件处理结果时,会延迟滚动。使用 .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 | 提升滚动性能,移动端优化 |
合理使用修饰符,能让代码更易读、更易维护。但也要避免过度使用,保持代码的简洁性。