Vue3 事件处理 v-on 指令 (@) 详解
核心概念理解
- 事件处理 | Vue.js
- 示例:表单处理、鼠标和触摸事件、防抖处理、节流处理
什么是事件处理?
事件处理就是响应用户的操作,比如点击按钮、输入文字、鼠标悬停等。Vue 通过 v-on
指令(简写为 @
)来监听和处理这些事件。
基础用法
1. 简单事件处理
vue
<template>
<div class="event-demo">
<h2>基础事件处理</h2>
<!-- 点击事件 -->
<button @click="handleClick">点击我</button>
<!-- 双击事件 -->
<button @dblclick="handleDoubleClick">双击我</button>
<!-- 鼠标悬停事件 -->
<div
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
class="hover-box"
>
鼠标悬停区域
</div>
<!-- 输入事件 -->
<input
@input="handleInput"
placeholder="输入文字试试"
class="input-field"
>
<p>输入内容: {{ inputValue }}</p>
<!-- 键盘事件 -->
<input
@keyup.enter="handleEnter"
@keyup.esc="handleEscape"
placeholder="按 Enter 或 Esc"
class="input-field"
>
</div>
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
const handleClick = () => {
alert('按钮被点击了!')
}
const handleDoubleClick = () => {
alert('按钮被双击了!')
}
const handleMouseEnter = () => {
console.log('鼠标进入')
}
const handleMouseLeave = () => {
console.log('鼠标离开')
}
const handleInput = (event) => {
inputValue.value = event.target.value
}
const handleEnter = () => {
alert('按下了 Enter 键')
}
const handleEscape = () => {
alert('按下了 Esc 键')
}
</script>
<style>
.event-demo {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.hover-box {
width: 200px;
height: 100px;
background-color: #e3f2fd;
border: 2px solid #2196f3;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
cursor: pointer;
transition: all 0.3s ease;
}
.hover-box:hover {
background-color: #2196f3;
color: white;
}
.input-field {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #0056b3;
}
</style>
事件修饰符
1. 常用事件修饰符
vue
<template>
<div class="modifier-demo">
<h2>事件修饰符</h2>
<!-- .prevent 阻止默认行为 -->
<form @submit.prevent="handleSubmit">
<input v-model="form.name" placeholder="姓名" required>
<button type="submit">提交表单</button>
</form>
<!-- .stop 阻止事件冒泡 -->
<div class="parent" @click="parentClick">
父元素
<div class="child" @click.stop="childClick">
子元素 (阻止冒泡)
</div>
</div>
<!-- .self 只在事件目标是元素自身时触发 -->
<div class="self-demo" @click.self="selfClick">
外层区域 (只点击这里才触发)
<div class="inner">
内层区域 (点击这里不触发)
</div>
</div>
<!-- .once 只触发一次 -->
<button @click.once="onceClick">
只能点击一次的按钮
</button>
<!-- .capture 事件捕获模式 -->
<div class="capture-demo" @click.capture="captureClick">
捕获阶段
<div class="capture-child" @click="childClick2">
目标阶段
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const form = ref({
name: ''
})
const handleSubmit = () => {
alert(`提交成功: ${form.value.name}`)
form.value.name = ''
}
const parentClick = () => {
console.log('父元素被点击')
}
const childClick = () => {
console.log('子元素被点击 (不会冒泡到父元素)')
}
const selfClick = () => {
alert('只在点击外层区域时触发')
}
const onceClick = () => {
alert('这个按钮只能点击一次')
}
const captureClick = () => {
console.log('捕获阶段: 外层被点击')
}
const childClick2 = () => {
console.log('目标阶段: 内层被点击')
}
</script>
<style>
.modifier-demo {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
form {
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
margin-bottom: 20px;
}
form input {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
}
.parent {
width: 300px;
height: 150px;
background-color: #d1ecf1;
border: 2px solid #bee5eb;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
position: relative;
}
.child {
width: 150px;
height: 75px;
background-color: #f8d7da;
border: 2px solid #f5c6cb;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.self-demo {
width: 300px;
height: 150px;
background-color: #d4edda;
border: 2px solid #c3e6cb;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
position: relative;
}
.inner {
width: 150px;
height: 75px;
background-color: #fff3cd;
border: 2px solid #ffeaa7;
display: flex;
align-items: center;
justify-content: center;
}
.capture-demo {
width: 300px;
height: 150px;
background-color: #e2e3e5;
border: 2px solid #d6d8db;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
}
.capture-child {
width: 150px;
height: 75px;
background-color: #cce7ff;
border: 2px solid #b8daff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>
2. 按键修饰符
vue
<template>
<div class="key-modifier-demo">
<h2>按键修饰符</h2>
<!-- 系统按键修饰符 -->
<div class="key-section">
<h3>系统按键</h3>
<input
@keyup.ctrl.enter="handleCtrlEnter"
placeholder="按 Ctrl + Enter"
class="input-field"
>
<input
@keyup.alt.c="handleAltC"
placeholder="按 Alt + C"
class="input-field"
>
<input
@keyup.shift.space="handleShiftSpace"
placeholder="按 Shift + 空格"
class="input-field"
>
</div>
<!-- 精确按键修饰符 -->
<div class="key-section">
<h3>精确按键</h3>
<input
@keyup.exact="handleExact"
placeholder="只按一个键"
class="input-field"
>
<input
@keyup.ctrl.exact="handleCtrlExact"
placeholder="只按 Ctrl 键"
class="input-field"
>
</div>
<!-- 常用按键别名 -->
<div class="key-section">
<h3>常用按键</h3>
<input
@keyup.enter="handleEnter"
placeholder="Enter 键"
class="input-field"
>
<input
@keyup.tab="handleTab"
placeholder="Tab 键"
class="input-field"
>
<input
@keyup.delete="handleDelete"
placeholder="Delete 键"
class="input-field"
>
<input
@keyup.esc="handleEscape"
placeholder="Esc 键"
class="input-field"
>
<input
@keyup.space="handleSpace"
placeholder="空格键"
class="input-field"
>
<input
@keyup.up="handleUp"
placeholder="上箭头"
class="input-field"
>
<input
@keyup.down="handleDown"
placeholder="下箭头"
class="input-field"
>
</div>
</div>
</template>
<script setup>
const handleCtrlEnter = () => {
alert('按下了 Ctrl + Enter')
}
const handleAltC = () => {
alert('按下了 Alt + C')
}
const handleShiftSpace = () => {
alert('按下了 Shift + 空格')
}
const handleExact = () => {
alert('只按了一个键')
}
const handleCtrlExact = () => {
alert('只按了 Ctrl 键')
}
const handleEnter = () => {
console.log('Enter 键被按下')
}
const handleTab = () => {
console.log('Tab 键被按下')
}
const handleDelete = () => {
console.log('Delete 键被按下')
}
const handleEscape = () => {
console.log('Esc 键被按下')
}
const handleSpace = () => {
console.log('空格键被按下')
}
const handleUp = () => {
console.log('上箭头被按下')
}
const handleDown = () => {
console.log('下箭头被按下')
}
</script>
<style>
.key-modifier-demo {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.key-section {
margin-bottom: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
}
.key-section h3 {
margin-top: 0;
color: #495057;
}
.input-field {
width: 100%;
padding: 12px;
margin: 10px 0;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 16px;
outline: none;
transition: border-color 0.2s ease;
}
.input-field:focus {
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
</style>
事件参数传递
1. 内联处理器中的参数
vue
<template>
<div class="param-demo">
<h2>事件参数传递</h2>
<!-- 传递静态参数 -->
<div class="button-group">
<button @click="greet('Alice')">问候 Alice</button>
<button @click="greet('Bob')">问候 Bob</button>
<button @click="greet('Charlie')">问候 Charlie</button>
</div>
<!-- 传递动态参数 -->
<div class="counter-demo">
<h3>计数器示例</h3>
<div v-for="counter in counters" :key="counter.id">
<span>{{ counter.name }}: {{ counter.value }}</span>
<button @click="increment(counter, 1)">+1</button>
<button @click="increment(counter, 5)">+5</button>
<button @click="increment(counter, -1)">-1</button>
<button @click="resetCounter(counter)">重置</button>
</div>
</div>
<!-- 传递原生事件对象 -->
<div class="event-object-demo">
<h3>事件对象</h3>
<input
@input="handleInputEvent"
placeholder="输入文字查看事件对象"
class="input-field"
>
<p>事件类型: {{ eventInfo.type }}</p>
<p>输入值: {{ eventInfo.value }}</p>
<p>按键: {{ eventInfo.key }}</p>
</div>
<!-- 方法处理器 -->
<div class="method-handler-demo">
<h3>方法处理器</h3>
<button @click="handleButtonClick">点击获取事件对象</button>
<p>按钮坐标: {{ buttonPosition.x }}, {{ buttonPosition.y }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const counters = ref([
{ id: 1, name: '计数器1', value: 0 },
{ id: 2, name: '计数器2', value: 0 },
{ id: 3, name: '计数器3', value: 0 }
])
const eventInfo = ref({
type: '',
value: '',
key: ''
})
const buttonPosition = ref({
x: 0,
y: 0
})
const greet = (name) => {
alert(`你好, ${name}!`)
}
const increment = (counter, amount) => {
counter.value += amount
}
const resetCounter = (counter) => {
counter.value = 0
}
const handleInputEvent = (event) => {
eventInfo.value = {
type: event.type,
value: event.target.value,
key: event.key
}
}
const handleButtonClick = (event) => {
buttonPosition.value = {
x: event.clientX,
y: event.clientY
}
console.log('事件对象:', event)
}
</script>
<style>
.param-demo {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.button-group {
margin-bottom: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
text-align: center;
}
.button-group button {
padding: 10px 15px;
margin: 5px;
border: none;
border-radius: 4px;
background-color: #28a745;
color: white;
cursor: pointer;
transition: background-color 0.2s ease;
}
.button-group button:hover {
background-color: #218838;
}
.counter-demo {
margin-bottom: 30px;
padding: 20px;
background-color: #fff;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.counter-demo div {
display: flex;
align-items: center;
gap: 10px;
margin: 10px 0;
padding: 10px;
background-color: #f8f9fa;
border-radius: 4px;
}
.counter-demo button {
padding: 5px 10px;
border: none;
border-radius: 3px;
background-color: #007bff;
color: white;
cursor: pointer;
font-size: 12px;
}
.counter-demo button:hover {
background-color: #0056b3;
}
.event-object-demo {
margin-bottom: 30px;
padding: 20px;
background-color: #e3f2fd;
border-radius: 8px;
}
.method-handler-demo {
padding: 20px;
background-color: #fff3cd;
border-radius: 8px;
}
.input-field {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 16px;
}
</style>
实际应用示例
1. 表单处理
vue
<template>
<div class="form-demo">
<h2>表单事件处理</h2>
<form @submit.prevent="handleSubmit" class="user-form">
<div class="form-group">
<label>用户名:</label>
<input
v-model="form.username"
@blur="validateUsername"
:class="{ 'error': errors.username }"
required
>
<span v-if="errors.username" class="error-message">
{{ errors.username }}
</span>
</div>
<div class="form-group">
<label>邮箱:</label>
<input
v-model="form.email"
@input="validateEmail"
:class="{ 'error': errors.email }"
type="email"
required
>
<span v-if="errors.email" class="error-message">
{{ errors.email }}
</span>
</div>
<div class="form-group">
<label>密码:</label>
<input
v-model="form.password"
@keyup.enter="handleSubmit"
:class="{ 'error': errors.password }"
type="password"
required
>
<span v-if="errors.password" class="error-message">
{{ errors.password }}
</span>
</div>
<div class="form-group">
<label>
<input
v-model="form.agree"
type="checkbox"
>
我同意用户协议
</label>
</div>
<div class="form-actions">
<button
type="submit"
:disabled="!isFormValid"
@click.ctrl.exact="submitWithCtrl"
>
提交 (Ctrl+点击快速提交)
</button>
<button @click="resetForm" type="button">重置</button>
</div>
</form>
<div v-if="submitted" class="success-message">
<h3>提交成功!</h3>
<p>用户名: {{ submittedData.username }}</p>
<p>邮箱: {{ submittedData.email }}</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const form = ref({
username: '',
email: '',
password: '',
agree: false
})
const errors = ref({
username: '',
email: '',
password: ''
})
const submitted = ref(false)
const submittedData = ref({})
// 表单验证
const validateUsername = () => {
if (form.value.username.length < 3) {
errors.value.username = '用户名至少3个字符'
} else {
errors.value.username = ''
}
}
const validateEmail = () => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(form.value.email)) {
errors.value.email = '请输入有效的邮箱地址'
} else {
errors.value.email = ''
}
}
const validatePassword = () => {
if (form.value.password.length < 6) {
errors.value.password = '密码至少6个字符'
} else {
errors.value.password = ''
}
}
// 计算属性:表单是否有效
const isFormValid = computed(() => {
return (
form.value.username.length >= 3 &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email) &&
form.value.password.length >= 6 &&
form.value.agree
)
})
// 表单提交
const handleSubmit = () => {
validateUsername()
validateEmail()
validatePassword()
if (isFormValid.value) {
submitted.value = true
submittedData.value = { ...form.value }
console.log('表单提交:', form.value)
} else {
alert('请检查表单信息')
}
}
// Ctrl+点击提交
const submitWithCtrl = (event) => {
event.preventDefault()
console.log('Ctrl+点击提交')
handleSubmit()
}
// 重置表单
const resetForm = () => {
form.value = {
username: '',
email: '',
password: '',
agree: false
}
errors.value = {
username: '',
email: '',
password: ''
}
submitted.value = false
}
</script>
<style>
.form-demo {
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.user-form {
background-color: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #495057;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 16px;
transition: border-color 0.2s ease;
}
.form-group input.error {
border-color: #dc3545;
}
.form-group input:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
.error-message {
color: #dc3545;
font-size: 14px;
margin-top: 5px;
display: block;
}
.form-group input[type="checkbox"] {
margin-right: 8px;
}
.form-actions {
display: flex;
gap: 10px;
margin-top: 30px;
}
.form-actions button {
padding: 12px 24px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.form-actions button[type="submit"] {
background-color: #28a745;
color: white;
flex: 1;
}
.form-actions button[type="submit"]:hover:not(:disabled) {
background-color: #218838;
}
.form-actions button[type="submit"]:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.form-actions button[type="button"] {
background-color: #6c757d;
color: white;
}
.form-actions button[type="button"]:hover {
background-color: #5a6268;
}
.success-message {
margin-top: 20px;
padding: 20px;
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 8px;
text-align: center;
}
</style>
2. 鼠标和触摸事件
vue
<template>
<div class="mouse-demo">
<h2>鼠标和触摸事件</h2>
<!-- 鼠标事件 -->
<div class="mouse-section">
<h3>鼠标事件</h3>
<div
class="mouse-area"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@mousemove="handleMouseMove"
@contextmenu.prevent="handleRightClick"
>
鼠标操作区域
<div class="mouse-info">
<p>鼠标位置: {{ mousePosition.x }}, {{ mousePosition.y }}</p>
<p>鼠标状态: {{ mouseStatus }}</p>
</div>
</div>
</div>
<!-- 拖拽功能 -->
<div class="drag-section">
<h3>拖拽功能</h3>
<div
class="draggable-box"
:style="{
left: boxPosition.x + 'px',
top: boxPosition.y + 'px',
cursor: isDragging ? 'grabbing' : 'grab'
}"
@mousedown="startDrag"
@touchstart="startDrag"
>
拖拽我
</div>
</div>
<!-- 滚轮事件 -->
<div class="wheel-section">
<h3>滚轮事件</h3>
<div
class="wheel-area"
@wheel.prevent="handleWheel"
:style="{ fontSize: fontSize + 'px' }"
>
使用鼠标滚轮调整字体大小
<p>当前字体大小: {{ fontSize }}px</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const mousePosition = ref({ x: 0, y: 0 })
const mouseStatus = ref('未按下')
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
const boxPosition = ref({ x: 50, y: 50 })
const fontSize = ref(16)
const handleMouseDown = (event) => {
mouseStatus.value = '按下'
updateMousePosition(event)
}
const handleMouseUp = (event) => {
mouseStatus.value = '释放'
updateMousePosition(event)
}
const handleMouseEnter = (event) => {
mouseStatus.value = '进入'
updateMousePosition(event)
}
const handleMouseLeave = (event) => {
mouseStatus.value = '离开'
updateMousePosition(event)
}
const handleMouseMove = (event) => {
updateMousePosition(event)
}
const handleRightClick = () => {
alert('右键菜单被阻止了')
}
const updateMousePosition = (event) => {
mousePosition.value = {
x: event.clientX,
y: event.clientY
}
}
// 拖拽功能
const startDrag = (event) => {
isDragging.value = true
const clientX = event.clientX || event.touches[0].clientX
const clientY = event.clientY || event.touches[0].clientY
dragStart.value = {
x: clientX - boxPosition.value.x,
y: clientY - boxPosition.value.y
}
document.addEventListener('mousemove', drag)
document.addEventListener('touchmove', drag)
document.addEventListener('mouseup', stopDrag)
document.addEventListener('touchend', stopDrag)
}
const drag = (event) => {
if (!isDragging.value) return
const clientX = event.clientX || event.touches[0].clientX
const clientY = event.clientY || event.touches[0].clientY
boxPosition.value = {
x: clientX - dragStart.value.x,
y: clientY - dragStart.value.y
}
}
const stopDrag = () => {
isDragging.value = false
document.removeEventListener('mousemove', drag)
document.removeEventListener('touchmove', drag)
document.removeEventListener('mouseup', stopDrag)
document.removeEventListener('touchend', stopDrag)
}
// 滚轮事件
const handleWheel = (event) => {
if (event.deltaY < 0) {
fontSize.value = Math.min(fontSize.value + 2, 40)
} else {
fontSize.value = Math.max(fontSize.value - 2, 12)
}
}
</script>
<style>
.mouse-demo {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.mouse-section, .drag-section, .wheel-section {
margin-bottom: 30px;
padding: 20px;
background-color: #fff;
border: 1px solid #dee2e6;
border-radius: 8px;
}
.mouse-area {
width: 100%;
height: 200px;
background-color: #e3f2fd;
border: 2px dashed #2196f3;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
user-select: none;
}
.mouse-info {
margin-top: 20px;
text-align: center;
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 4px;
}
.drag-section {
position: relative;
height: 300px;
overflow: hidden;
}
.draggable-box {
position: absolute;
width: 100px;
height: 100px;
background-color: #ff6b6b;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
user-select: none;
transition: none;
}
.wheel-area {
width: 100%;
height: 200px;
background-color: #d4edda;
border: 2px solid #c3e6cb;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
user-select: none;
}
</style>
注意事项和最佳实践
1. 事件处理中的 this 绑定
vue
<template>
<div class="this-demo">
<h2>this 绑定问题</h2>
<!-- ❌ 错误示例:箭头函数可能导致 this 问题 -->
<button @click="wrongHandler">错误处理</button>
<!-- ✅ 正确示例:使用普通函数 -->
<button @click="correctHandler">正确处理</button>
<!-- ✅ 正确示例:使用箭头函数但不依赖 this -->
<button @click="() => console.log('箭头函数')">箭头函数</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue3!')
// ❌ 可能有问题的处理方式
const wrongHandler = () => {
// 在 setup 中,this 是 undefined
console.log(this) // undefined
}
// ✅ 推荐的处理方式
const correctHandler = () => {
console.log(message.value) // 直接使用响应式变量
}
</script>
2. 事件性能优化
vue
<template>
<div class="performance-demo">
<h2>事件性能优化</h2>
<!-- ✅ 使用 .once 修饰符 -->
<button @click.once="handleOnce">只执行一次</button>
<!-- ✅ 使用 .passive 修饰符提高滚动性能 -->
<div
class="scroll-area"
@scroll.passive="handleScroll"
>
<div class="scroll-content">
<div
v-for="item in 100"
:key="item"
class="scroll-item"
>
项目 {{ item }}
</div>
</div>
</div>
<!-- ✅ 防抖处理 -->
<div class="debounce-demo">
<input
@input="debouncedInput"
placeholder="输入文字(防抖)"
class="input-field"
>
<p>防抖结果: {{ debounceResult }}</p>
</div>
<!-- ✅ 节流处理 -->
<div class="throttle-demo">
<div
@mousemove="throttledMouseMove"
class="throttle-area"
>
鼠标移动区域(节流)
<p>节流结果: {{ throttleResult }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const debounceResult = ref('')
const throttleResult = ref('')
let debounceTimer = null
let throttleTimer = null
let lastThrottleTime = 0
const throttleDelay = 100
const handleOnce = () => {
alert('这个按钮只能点击一次')
}
const handleScroll = (event) => {
console.log('滚动事件触发')
}
// 防抖处理
const debouncedInput = (event) => {
clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
debounceResult.value = event.target.value
}, 300)
}
// 节流处理
const throttledMouseMove = (event) => {
const now = Date.now()
if (now - lastThrottleTime > throttleDelay) {
throttleResult.value = `X: ${event.clientX}, Y: ${event.clientY}`
lastThrottleTime = now
}
}
</script>
<style>
.performance-demo {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.scroll-area {
height: 200px;
overflow-y: auto;
border: 1px solid #ced4da;
border-radius: 4px;
margin: 20px 0;
}
.scroll-content {
padding: 10px;
}
.scroll-item {
padding: 10px;
margin: 5px 0;
background-color: #f8f9fa;
border-radius: 4px;
}
.debounce-demo, .throttle-demo {
margin: 20px 0;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
}
.throttle-area {
height: 150px;
background-color: #e3f2fd;
border: 2px dashed #2196f3;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 10px;
}
.input-field {
width: 100%;
padding: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 10px 20px;
margin: 5px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
font-size: 16px;
}
</style>
总结
事件修饰符速查表
修饰符 | 作用 |
---|---|
.prevent |
阻止默认行为 |
.stop |
阻止事件冒泡 |
.self |
只在事件目标是元素自身时触发 |
.once |
只触发一次 |
.capture |
使用事件捕获模式 |
.passive |
以 passive 模式添加事件 |
按键修饰符速查表
修饰符 | 对应按键 |
---|---|
.enter |
Enter |
.tab |
Tab |
.delete |
Delete 或 Backspace |
.esc |
Escape |
.space |
空格 |
.up |
上箭头 |
.down |
下箭头 |
.left |
左箭头 |
.right |
右箭头 |
.ctrl |
Ctrl |
.alt |
Alt |
.shift |
Shift |
.meta |
Meta (Mac: ⌘, Windows: ⊞) |
使用建议
- 优先使用简写 :
@click
而不是v-on:click
- 合理使用修饰符:避免在 JavaScript 中处理本可以用修饰符解决的问题
- 注意性能:对于频繁触发的事件使用防抖或节流
- 事件委托:对于大量相似元素,考虑在父元素上处理事件
- 清理事件:记得在组件销毁时清理手动添加的事件监听器
记忆口诀:
- @click:点击事件最常用
- @input:输入实时响应
- @submit.prevent:表单提交防跳转
- @keyup.enter:回车键触发
- 修饰符连用:功能组合更强大