Appearance
事件处理
Vue 使用 v-on 指令(简写为 @)来监听 DOM 事件,并在事件触发时执行相应的 JavaScript 代码。
基本用法
内联事件处理器
可以直接在 v-on 指令中编写简单的 JavaScript 表达式:
vue
<template>
<div>
<p>{{ count }}</p>
<button @click="count++">增加 1</button>
<button @click="count--">减少 1</button>
<button @click="count = 0">重置</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>方法事件处理器
对于复杂的逻辑,应该使用方法事件处理器:
vue
<template>
<div>
<p>{{ message }}</p>
<button @click="greet">问候</button>
<button @click="say('hello')">说 Hello</button>
<button @click="say('goodbye')">说 Goodbye</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue!')
const greet = () => {
alert('Hello!')
}
const say = (msg) => {
message.value = msg
}
</script>事件对象
访问原生事件
在内联事件处理器中,可以使用特殊的 $event 变量访问原生 DOM 事件:
vue
<template>
<div>
<button @click="warn('Form cannot be submitted yet.', $event)">
提交
</button>
<input @keyup="handleKeyup($event)" placeholder="按任意键..." />
</div>
</template>
<script setup>
const warn = (message, event) => {
// 现在可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
const handleKeyup = (event) => {
console.log('按下的键:', event.key)
console.log('键码:', event.keyCode)
}
</script>方法中的事件对象
在方法事件处理器中,事件对象会自动作为第一个参数传入:
vue
<template>
<div>
<form @submit="handleSubmit">
<input v-model="name" placeholder="姓名" required />
<button type="submit">提交</button>
</form>
<div @click="handleClick">
<button @click="handleButtonClick">按钮</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const name = ref('')
const handleSubmit = (event) => {
event.preventDefault()
console.log('提交的姓名:', name.value)
}
const handleClick = (event) => {
console.log('点击了 div')
}
const handleButtonClick = (event) => {
event.stopPropagation() // 阻止事件冒泡
console.log('点击了按钮')
}
</script>事件修饰符
Vue 为 v-on 提供了事件修饰符来处理常见的 DOM 事件细节。
基本修饰符
vue
<template>
<div>
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis">链接</a>
<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit">
<button type="submit">提交</button>
</form>
<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat">链接</a>
<!-- 只有修饰符 -->
<form @submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<div @click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<div @click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a @click.once="doThis">链接</a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<div @scroll.passive="onScroll">...</div>
</div>
</template>
<script setup>
const doThis = () => {
console.log('doThis')
}
const doThat = () => {
console.log('doThat')
}
const onSubmit = () => {
console.log('表单提交')
}
const onScroll = () => {
console.log('滚动事件')
}
</script>修饰符详解
.stop
阻止事件冒泡:
vue
<template>
<div @click="parentClick" class="parent">
父元素
<button @click.stop="childClick" class="child">
子元素(不会触发父元素事件)
</button>
</div>
</template>
<script setup>
const parentClick = () => {
console.log('父元素被点击')
}
const childClick = () => {
console.log('子元素被点击')
}
</script>.prevent
阻止默认行为:
vue
<template>
<div>
<!-- 阻止表单默认提交行为 -->
<form @submit.prevent="handleSubmit">
<input type="text" v-model="input" />
<button type="submit">提交</button>
</form>
<!-- 阻止链接默认跳转行为 -->
<a href="https://vuejs.org" @click.prevent="handleLinkClick">
Vue.js 官网
</a>
</div>
</template>
<script setup>
import { ref } from 'vue'
const input = ref('')
const handleSubmit = () => {
console.log('自定义提交逻辑')
}
const handleLinkClick = () => {
console.log('自定义链接点击逻辑')
}
</script>.self
只在事件目标是元素自身时触发:
vue
<template>
<div @click.self="handleSelfClick" class="container">
点击这个区域(不包括按钮)
<button @click="handleButtonClick">按钮</button>
</div>
</template>
<script setup>
const handleSelfClick = () => {
console.log('只有点击 div 自身才会触发')
}
const handleButtonClick = () => {
console.log('按钮被点击')
}
</script>.once
事件只触发一次:
vue
<template>
<div>
<button @click.once="handleOnce">只能点击一次</button>
<button @click="handleMultiple">可以多次点击</button>
</div>
</template>
<script setup>
const handleOnce = () => {
console.log('这个事件只会触发一次')
}
const handleMultiple = () => {
console.log('这个事件可以多次触发')
}
</script>按键修饰符
基本按键修饰符
vue
<template>
<div>
<!-- 只有在 `key` 是 `Enter` 时调用 `submit` -->
<input @keyup.enter="submit" placeholder="按 Enter 提交" />
<!-- 只有在 `key` 是 `Escape` 时调用 `clear` -->
<input @keyup.esc="clear" placeholder="按 Esc 清空" />
<!-- 只有在 `key` 是 `Space` 时调用 `handleSpace` -->
<input @keyup.space="handleSpace" placeholder="按空格键" />
<!-- 只有在 `key` 是 `Tab` 时调用 `handleTab` -->
<input @keydown.tab="handleTab" placeholder="按 Tab 键" />
<!-- 只有在 `key` 是 `Delete` 或 `Backspace` 时调用 `handleDelete` -->
<input @keyup.delete="handleDelete" placeholder="按删除键" />
</div>
</template>
<script setup>
const submit = () => {
console.log('提交')
}
const clear = () => {
console.log('清空')
}
const handleSpace = () => {
console.log('空格键被按下')
}
const handleTab = () => {
console.log('Tab 键被按下')
}
const handleDelete = () => {
console.log('删除键被按下')
}
</script>系统修饰符
vue
<template>
<div>
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" placeholder="Alt + Enter" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Ctrl + 点击</div>
<!-- Shift + Click -->
<div @click.shift="doSomething">Shift + 点击</div>
<!-- Meta + Click (Mac 上的 Cmd 键,Windows 上的 Windows 键) -->
<div @click.meta="doSomething">Meta + 点击</div>
</div>
</template>
<script setup>
const clear = () => {
console.log('Alt + Enter')
}
const doSomething = () => {
console.log('系统键 + 点击')
}
</script>.exact 修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件:
vue
<template>
<div>
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">B</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">C</button>
</div>
</template>
<script setup>
const onClick = () => {
console.log('普通点击')
}
const onCtrlClick = () => {
console.log('只有 Ctrl + 点击')
}
</script>鼠标按钮修饰符
vue
<template>
<div>
<!-- 左键点击 -->
<button @click.left="handleLeftClick">左键点击</button>
<!-- 右键点击 -->
<button @click.right="handleRightClick">右键点击</button>
<!-- 中键点击 -->
<button @click.middle="handleMiddleClick">中键点击</button>
</div>
</template>
<script setup>
const handleLeftClick = () => {
console.log('左键点击')
}
const handleRightClick = () => {
console.log('右键点击')
}
const handleMiddleClick = () => {
console.log('中键点击')
}
</script>常见事件类型
表单事件
vue
<template>
<form @submit.prevent="handleSubmit">
<!-- 输入事件 -->
<input
v-model="username"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
placeholder="用户名"
/>
<!-- 选择事件 -->
<select @change="handleSelectChange" v-model="selectedOption">
<option value="">请选择</option>
<option value="option1">选项 1</option>
<option value="option2">选项 2</option>
</select>
<!-- 复选框事件 -->
<label>
<input
type="checkbox"
v-model="agreed"
@change="handleCheckboxChange"
/>
我同意条款
</label>
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const selectedOption = ref('')
const agreed = ref(false)
const handleSubmit = () => {
console.log('提交表单:', {
username: username.value,
selectedOption: selectedOption.value,
agreed: agreed.value
})
}
const handleInput = (event) => {
console.log('输入中:', event.target.value)
}
const handleFocus = () => {
console.log('获得焦点')
}
const handleBlur = () => {
console.log('失去焦点')
}
const handleChange = () => {
console.log('值改变:', username.value)
}
const handleSelectChange = () => {
console.log('选择改变:', selectedOption.value)
}
const handleCheckboxChange = () => {
console.log('复选框状态:', agreed.value)
}
</script>鼠标事件
vue
<template>
<div
class="mouse-area"
@click="handleClick"
@dblclick="handleDoubleClick"
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
@mouseover="handleMouseOver"
@mouseout="handleMouseOut"
@mousemove="handleMouseMove"
@contextmenu.prevent="handleContextMenu"
>
<p>鼠标事件测试区域</p>
<p>坐标: ({{ mouseX }}, {{ mouseY }})</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const mouseX = ref(0)
const mouseY = ref(0)
const handleClick = (event) => {
console.log('单击', event.clientX, event.clientY)
}
const handleDoubleClick = () => {
console.log('双击')
}
const handleMouseDown = () => {
console.log('鼠标按下')
}
const handleMouseUp = () => {
console.log('鼠标释放')
}
const handleMouseOver = () => {
console.log('鼠标进入')
}
const handleMouseOut = () => {
console.log('鼠标离开')
}
const handleMouseMove = (event) => {
mouseX.value = event.clientX
mouseY.value = event.clientY
}
const handleContextMenu = () => {
console.log('右键菜单')
}
</script>
<style scoped>
.mouse-area {
width: 300px;
height: 200px;
border: 2px solid #ccc;
padding: 20px;
margin: 20px 0;
background-color: #f9f9f9;
cursor: pointer;
}
</style>键盘事件
vue
<template>
<div>
<input
v-model="inputValue"
@keydown="handleKeyDown"
@keyup="handleKeyUp"
@keypress="handleKeyPress"
placeholder="键盘事件测试"
/>
<p>最后按下的键: {{ lastKey }}</p>
<p>按键次数: {{ keyCount }}</p>
<!-- 特定按键处理 -->
<input
@keyup.enter="handleEnter"
@keyup.esc="handleEscape"
@keyup.arrow-up="handleArrowUp"
@keyup.arrow-down="handleArrowDown"
placeholder="特定按键测试"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
const inputValue = ref('')
const lastKey = ref('')
const keyCount = ref(0)
const handleKeyDown = (event) => {
console.log('按键按下:', event.key)
}
const handleKeyUp = (event) => {
lastKey.value = event.key
keyCount.value++
console.log('按键释放:', event.key)
}
const handleKeyPress = (event) => {
console.log('按键按压:', event.key)
}
const handleEnter = () => {
console.log('Enter 键被按下')
}
const handleEscape = () => {
console.log('Escape 键被按下')
inputValue.value = ''
}
const handleArrowUp = () => {
console.log('上箭头键被按下')
}
const handleArrowDown = () => {
console.log('下箭头键被按下')
}
</script>实际应用示例
搜索框
vue
<template>
<div class="search-container">
<div class="search-box">
<input
v-model="searchQuery"
@input="handleInput"
@keyup.enter="handleSearch"
@keyup.esc="clearSearch"
@focus="showSuggestions = true"
@blur="hideSuggestions"
placeholder="搜索..."
class="search-input"
/>
<button @click="handleSearch" class="search-button">
搜索
</button>
</div>
<div v-if="showSuggestions && suggestions.length" class="suggestions">
<div
v-for="(suggestion, index) in suggestions"
:key="index"
@mousedown.prevent="selectSuggestion(suggestion)"
class="suggestion-item"
>
{{ suggestion }}
</div>
</div>
<div v-if="searchResults.length" class="results">
<h3>搜索结果:</h3>
<ul>
<li v-for="result in searchResults" :key="result.id">
{{ result.title }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const searchQuery = ref('')
const showSuggestions = ref(false)
const searchResults = ref([])
const allSuggestions = [
'Vue.js',
'React',
'Angular',
'JavaScript',
'TypeScript',
'Node.js',
'Express',
'MongoDB'
]
const suggestions = computed(() => {
if (!searchQuery.value) return []
return allSuggestions.filter(item =>
item.toLowerCase().includes(searchQuery.value.toLowerCase())
).slice(0, 5)
})
const handleInput = () => {
// 实时搜索建议
showSuggestions.value = true
}
const handleSearch = () => {
if (searchQuery.value.trim()) {
// 模拟搜索结果
searchResults.value = [
{ id: 1, title: `关于 "${searchQuery.value}" 的结果 1` },
{ id: 2, title: `关于 "${searchQuery.value}" 的结果 2` },
{ id: 3, title: `关于 "${searchQuery.value}" 的结果 3` }
]
showSuggestions.value = false
}
}
const clearSearch = () => {
searchQuery.value = ''
searchResults.value = []
showSuggestions.value = false
}
const selectSuggestion = (suggestion) => {
searchQuery.value = suggestion
handleSearch()
}
const hideSuggestions = () => {
// 延迟隐藏,以便点击建议项
setTimeout(() => {
showSuggestions.value = false
}, 200)
}
</script>
<style scoped>
.search-container {
position: relative;
max-width: 500px;
margin: 20px auto;
}
.search-box {
display: flex;
border: 2px solid #ddd;
border-radius: 25px;
overflow: hidden;
}
.search-input {
flex: 1;
padding: 12px 20px;
border: none;
outline: none;
font-size: 16px;
}
.search-button {
padding: 12px 20px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
font-size: 16px;
}
.search-button:hover {
background-color: #0056b3;
}
.suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-top: none;
border-radius: 0 0 8px 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.suggestion-item {
padding: 12px 20px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.suggestion-item:hover {
background-color: #f5f5f5;
}
.suggestion-item:last-child {
border-bottom: none;
}
.results {
margin-top: 20px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
}
.results ul {
list-style: none;
padding: 0;
}
.results li {
padding: 8px 0;
border-bottom: 1px solid #ddd;
}
.results li:last-child {
border-bottom: none;
}
</style>拖拽功能
vue
<template>
<div class="drag-container">
<h3>拖拽示例</h3>
<div
class="draggable-item"
:style="{ transform: `translate(${position.x}px, ${position.y}px)` }"
@mousedown="startDrag"
>
拖拽我
</div>
<div class="info">
<p>位置: ({{ position.x }}, {{ position.y }})</p>
<p>状态: {{ isDragging ? '拖拽中' : '静止' }}</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const position = ref({ x: 0, y: 0 })
const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 })
const startDrag = (event) => {
isDragging.value = true
dragStart.value = {
x: event.clientX - position.value.x,
y: event.clientY - position.value.y
}
document.addEventListener('mousemove', onDrag)
document.addEventListener('mouseup', stopDrag)
}
const onDrag = (event) => {
if (isDragging.value) {
position.value = {
x: event.clientX - dragStart.value.x,
y: event.clientY - dragStart.value.y
}
}
}
const stopDrag = () => {
isDragging.value = false
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
}
onUnmounted(() => {
document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag)
})
</script>
<style scoped>
.drag-container {
position: relative;
height: 400px;
border: 2px dashed #ccc;
margin: 20px 0;
overflow: hidden;
}
.draggable-item {
position: absolute;
width: 100px;
height: 100px;
background-color: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
cursor: move;
user-select: none;
transition: transform 0.1s ease;
}
.draggable-item:hover {
background-color: #0056b3;
}
.info {
position: absolute;
top: 10px;
right: 10px;
background: rgba(255, 255, 255, 0.9);
padding: 10px;
border-radius: 4px;
font-size: 14px;
}
</style>模态框
vue
<template>
<div>
<button @click="showModal = true" class="open-modal-btn">
打开模态框
</button>
<div
v-if="showModal"
class="modal-overlay"
@click.self="closeModal"
@keyup.esc="closeModal"
tabindex="0"
>
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>模态框标题</h3>
<button @click="closeModal" class="close-btn">×</button>
</div>
<div class="modal-body">
<p>这是模态框的内容。</p>
<p>按 ESC 键或点击外部区域可以关闭模态框。</p>
<form @submit.prevent="handleSubmit">
<input
v-model="formData.name"
placeholder="姓名"
required
class="form-input"
/>
<input
v-model="formData.email"
type="email"
placeholder="邮箱"
required
class="form-input"
/>
</form>
</div>
<div class="modal-footer">
<button @click="closeModal" class="btn btn-secondary">
取消
</button>
<button @click="handleSubmit" class="btn btn-primary">
确认
</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick } from 'vue'
const showModal = ref(false)
const formData = ref({
name: '',
email: ''
})
const closeModal = () => {
showModal.value = false
// 重置表单
formData.value = {
name: '',
email: ''
}
}
const handleSubmit = () => {
if (formData.value.name && formData.value.email) {
console.log('提交数据:', formData.value)
closeModal()
}
}
// 当模态框打开时,聚焦到模态框容器以便键盘事件生效
watch(showModal, async (newVal) => {
if (newVal) {
await nextTick()
document.querySelector('.modal-overlay')?.focus()
}
})
</script>
<style scoped>
.open-modal-btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
outline: none;
}
.modal-content {
background: white;
border-radius: 8px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
}
.modal-header h3 {
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
color: #333;
}
.modal-body {
padding: 20px;
}
.form-input {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 20px;
border-top: 1px solid #eee;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn:hover {
opacity: 0.9;
}
</style>性能优化
事件委托
对于大量相似元素的事件处理,使用事件委托可以提高性能:
vue
<template>
<div @click="handleListClick" class="list-container">
<div
v-for="item in items"
:key="item.id"
:data-id="item.id"
:data-action="item.action"
class="list-item"
>
{{ item.name }}
<button :data-action="'edit'" :data-id="item.id">编辑</button>
<button :data-action="'delete'" :data-id="item.id">删除</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '项目 1', action: 'view' },
{ id: 2, name: '项目 2', action: 'view' },
{ id: 3, name: '项目 3', action: 'view' }
])
const handleListClick = (event) => {
const target = event.target
const action = target.dataset.action
const id = target.dataset.id
if (action && id) {
switch (action) {
case 'edit':
console.log('编辑项目:', id)
break
case 'delete':
console.log('删除项目:', id)
items.value = items.value.filter(item => item.id !== parseInt(id))
break
case 'view':
console.log('查看项目:', id)
break
}
}
}
</script>
<style scoped>
.list-container {
max-width: 400px;
margin: 20px auto;
}
.list-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border: 1px solid #ddd;
margin-bottom: 5px;
border-radius: 4px;
cursor: pointer;
}
.list-item:hover {
background-color: #f5f5f5;
}
.list-item button {
margin-left: 5px;
padding: 5px 10px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.list-item button[data-action="edit"] {
background-color: #28a745;
color: white;
}
.list-item button[data-action="delete"] {
background-color: #dc3545;
color: white;
}
</style>防抖和节流
对于频繁触发的事件,使用防抖和节流可以提高性能:
vue
<template>
<div>
<h3>防抖和节流示例</h3>
<!-- 防抖搜索 -->
<div class="section">
<h4>防抖搜索 (500ms)</h4>
<input
v-model="searchTerm"
@input="debouncedSearch"
placeholder="输入搜索关键词..."
/>
<p>搜索次数: {{ searchCount }}</p>
</div>
<!-- 节流滚动 -->
<div class="section">
<h4>节流滚动 (100ms)</h4>
<div
class="scroll-area"
@scroll="throttledScroll"
>
<div class="scroll-content">
<p v-for="i in 50" :key="i">滚动内容 {{ i }}</p>
</div>
</div>
<p>滚动事件触发次数: {{ scrollCount }}</p>
</div>
<!-- 防抖按钮 -->
<div class="section">
<h4>防抖按钮 (1000ms)</h4>
<button @click="debouncedSave">保存 (防抖)</button>
<p>保存次数: {{ saveCount }}</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const searchTerm = ref('')
const searchCount = ref(0)
const scrollCount = ref(0)
const saveCount = ref(0)
// 防抖函数
const debounce = (func, delay) => {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(null, args), delay)
}
}
// 节流函数
const throttle = (func, delay) => {
let lastCall = 0
return (...args) => {
const now = Date.now()
if (now - lastCall >= delay) {
lastCall = now
func.apply(null, args)
}
}
}
// 搜索函数
const search = () => {
searchCount.value++
console.log('搜索:', searchTerm.value)
}
// 滚动函数
const scroll = () => {
scrollCount.value++
console.log('滚动事件触发')
}
// 保存函数
const save = () => {
saveCount.value++
console.log('保存数据')
}
// 创建防抖和节流版本
const debouncedSearch = debounce(search, 500)
const throttledScroll = throttle(scroll, 100)
const debouncedSave = debounce(save, 1000)
</script>
<style scoped>
.section {
margin: 20px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.section h4 {
margin-top: 0;
}
.section input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.scroll-area {
height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
}
.scroll-content {
height: 1000px;
}
.section button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.section button:hover {
background-color: #0056b3;
}
.section p {
margin: 10px 0;
color: #666;
}
</style>选项式 API
在选项式 API 中,事件处理的用法基本相同:
vue
<template>
<div>
<button @click="handleClick">点击我</button>
<input @keyup.enter="handleEnter" v-model="message" />
</div>
</template>
<script>
export default {
data() {
return {
message: ''
}
},
methods: {
handleClick(event) {
console.log('按钮被点击', event)
},
handleEnter() {
console.log('Enter 键被按下:', this.message)
}
}
}
</script>最佳实践
1. 使用方法而不是内联表达式处理复杂逻辑
vue
<!-- 不推荐 -->
<button @click="count++; updateHistory(); saveToStorage()">
复杂操作
</button>
<!-- 推荐 -->
<button @click="handleComplexOperation">
复杂操作
</button>
<script setup>
const handleComplexOperation = () => {
count.value++
updateHistory()
saveToStorage()
}
</script>2. 合理使用事件修饰符
vue
<!-- 好的做法 -->
<form @submit.prevent="handleSubmit">
<button type="submit">提交</button>
</form>
<!-- 避免在方法中手动调用 preventDefault -->
<form @submit="handleSubmitWithPrevent">
<button type="submit">提交</button>
</form>
<script setup>
// 不推荐
const handleSubmitWithPrevent = (event) => {
event.preventDefault()
// 处理逻辑
}
// 推荐
const handleSubmit = () => {
// 处理逻辑
}
</script>3. 避免在模板中使用复杂表达式
vue
<!-- 不推荐 -->
<button @click="items.filter(item => item.active).forEach(item => item.process())">
处理
</button>
<!-- 推荐 -->
<button @click="processActiveItems">
处理
</button>
<script setup>
const processActiveItems = () => {
items.value
.filter(item => item.active)
.forEach(item => item.process())
}
</script>4. 正确清理事件监听器
vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
let resizeHandler
onMounted(() => {
resizeHandler = () => {
console.log('窗口大小改变')
}
window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {
if (resizeHandler) {
window.removeEventListener('resize', resizeHandler)
}
})
</script>