[TOC]
事件
1. 组件事件
1.1 基础事件触发
父组件 ParentComponent.vue
<template>
<ChildComponent @submit="handleSubmit" @cancel="handleCancel" />
</template>
<script setup>
const handleSubmit = (payload) => {
console.log('收到提交数据:', payload)
}
const handleCancel = () => {
console.log('取消操作')
}
</script>
子组件 ChildComponent.vue
<script setup>
// 1. 声明组件可触发的事件
const emit = defineEmits(['submit', 'cancel'])
// 2. 触发无参数事件
const handleCancel = () => { emit('cancel') }
// 3. 触发带参数事件
const handleSubmit = () => {
emit('submit', { username: 'admin', timestamp: Date.now() })
}
</script>
<template>
<button @click="handleSubmit">提交</button>
</template>
1.2 TypeScript 类型声明
子组件(带类型约束)
<script setup lang="ts">
// 定义事件类型
interface Emits {
(e: 'update:modelValue', value: string): void
(e: 'search', keyword: string, page: number): void
(e: 'error'): void
}
const emit = defineEmits<Emits>()
const handleSearch = () => {
if (keyword.value.length > 0) {
emit('search', keyword.value, currentPage.value)
} else {
emit('error')
}
}
</script>
1.3 事件验证(高级用法)
<script setup>
const emit = defineEmits({
// 带验证的事件
'page-change': (page) => {
// 返回布尔值表示验证是否通过
return Number.isInteger(page) && page > 0
}
})
const changePage = (newPage) => {
if (newPage !== currentPage.value) {
emit('page-change', newPage)
}
}
</script>
1.4 v-model 双向绑定
1、单个 v-model
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['update:modelValue'])
const updateValue = (newVal) => {
emit('update:modelValue', newVal)
}
</script>
<!-- 父组件 -->
<ChildComponent v-model="value" />
2、多个 v-model
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['update:name', 'update:age'])
const updateName = (val) => emit('update:name', val)
const updateAge = (val) => emit('update:age', val)
</script>
<!-- 父组件 -->
<ChildComponent v-model:name="userName" v-model:age="userAge" />
1.5 事件使用模式对比
模式 | 示例代码 | 适用场景 |
---|---|---|
基础事件 | emit('event-name') | 简单状态通知 |
数据传递事件 | emit('search', keyword) | 提交表单数据 |
复合参数事件 | emit('position', x, y) | 传递多个关联参数 |
验证事件 | 带验证的 defineEmits | 需要保证事件数据合法性 |
v-model 事件 | update:modelValue | 实现双向数据绑定 |
1.6 最佳实践
命名规范:
- 使用 kebab-case 命名事件(如
form-submit
) - 避免使用原生事件名(如
click
)
- 使用 kebab-case 命名事件(如
参数设计:
- 复杂数据使用对象包装
emit('user-update', { id: 123, newName: 'Alice' })
类型安全:
// 使用泛型约束 defineEmits<{ (e: 'upload-success', file: File): void (e: 'upload-error', error: Error): void }>()
1.7 常见问题
为什么需要 defineEmits?
- 明确组件接口
- 更好的类型推断
- 与模板自动扫描兼容
如何传递多个参数?
emit('position-change', x, y) // 父组件接收: @position-change="(x, y) => handlePosition(x, y)"
如何监听原生事件?
<!-- 子组件 --> <button @click="$emit('custom-click', $event)"> 按钮 </button> <!-- 父组件 --> <ChildComponent @custom-click="handleClick" />
2. 处理原生事件
2.1 基础事件绑定
<template>
<!-- 点击事件 -->
<button @click="handleClick">点击我</button>
<!-- 输入事件 -->
<input @input="handleInput">
<!-- 键盘事件 -->
<input @keyup.enter="submitForm">
</template>
<script setup>
const handleClick = (event) => {}
const handleInput = (event) => {}
const submitForm = () => {}
</script>
2.2 事件修饰符
修饰符 | 作用 | 示例 |
---|---|---|
.stop | 阻止事件冒泡 | @click.stop="handler" |
.prevent | 阻止默认行为 | @submit.prevent="handler" |
.capture | 使用捕获模式 | @click.capture="handler" |
.self | 仅当事件源是自身时触发 | @click.self="handler" |
.once | 只触发一次 | @click.once="handler" |
.passive | 提升滚动性能 | @scroll.passive="handler" |
组合使用示例
<a href="/about" @click.prevent.stop="handleLinkClick">
阻止跳转和冒泡
</a>
2.3 按键修饰符
Vue 3 内置了以下常见按键的别名(无需记忆键值):
修饰符 | 对应按键 |
---|---|
.enter | Enter 回车键 |
.tab | Tab 键 |
.delete | Delete 或 Backspace |
.esc | Esc 键 |
.space | 空格键 |
.up | 方向键 ↑ |
.down | 方向键 ↓ |
.left | 方向键 ← |
.right | 方向键 → |
<input
@keyup.enter="submit"
@keyup.ctrl.s="save"
@keydown.tab="nextField"
>
<!-- 自定义按键别名 -->
<script>
// 全局定义(main.js)
app.config.globalProperties.$customKeys = {
vkSave: 83 // s 键
}
</script>
2.4 系统修饰键
修饰符 | 对应按键 | 典型应用场景 |
---|---|---|
.ctrl | Ctrl 键 | 组合快捷键(如 Ctrl+C) |
.alt | Alt 键 | 菜单快捷键或组合操作 |
.shift | Shift 键 | 大小写切换或扩展选择 |
.meta | Windows 键或 Mac 的 Command 键 | 系统级快捷操作 |
<div @click.ctrl.exact="ctrlClick">Ctrl + 点击</div>
<div @click.alt.shift="altShiftClick">Alt+Shift+点击</div>
<!-- exact 修饰符示例 -->
<button
@click.exact="onlyClick"
@click.ctrl="ctrlClick">
精确控制
</button>
2.5 自定义组件中的原生事件
方案1:自动继承(默认绑定到根元素)
<!-- CustomButton.vue -->
<template>
<button class="custom-btn">
<slot></slot>
</button>
</template>
<!-- 父组件使用 -->
<CustomButton @click="handleButtonClick" />
方案2:手动绑定(更精细控制)
<!-- CustomInput.vue -->
<template>
<div class="input-wrapper">
<input v-bind="$attrs" @input="$emit('update:modelValue', $event.target.value)">
</div>
</template>
<script>
// 禁用自动继承
defineOptions({
inheritAttrs: false
})
</script>
<!-- 父组件使用 -->
<CustomInput
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
/>
2.6 组合式API中的事件处理
<template>
<div ref="elementRef">事件目标</div>
</template>
<script setup>
import { onMounted } from 'vue'
// 1. 模板引用
const elementRef = ref(null)
// 2. 手动添加事件监听
onMounted(() => {
elementRef.value.addEventListener('custom-event', handleCustom)
})
// 3. 清理事件
onUnmounted(() => {
elementRef.value?.removeEventListener('custom-event', handleCustom)
})
// 4. 处理函数
const handleCustom = (event) => {
console.log('收到自定义事件:', event.detail)
}
</script>
2.7 原生事件对象的使用
<template>
<input @input="logEvent">
</template>
<script setup>
const logEvent = (event) => {
// 获取事件目标值
console.log(event.target.value)
// 获取键盘事件键位
if (event.type === 'keyup') {
console.log('按键代码:', event.keyCode)
}
// 阻止默认行为(替代 preventDefault())
event.preventDefault()
}
</script>
2.8 高级场景:第三方库集成
<template>
<div ref="chartContainer"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'
const chartContainer = ref(null)
let chartInstance = null
onMounted(() => {
chartInstance = echarts.init(chartContainer.value)
// 监听图表点击事件
chartInstance.on('click', (params) => {
console.log('图表点击:', params)
})
})
// 清理事件
onUnmounted(() => {
chartInstance?.dispose()
})
</script>
2.9 最佳实践建议
优先使用修饰符:替代手动调用
event.preventDefault()
合理使用事件委托:对列表元素使用事件委托提升性能
及时清理事件:在组件卸载时移除手动绑定的事件
类型安全(TS):
interface CustomEventDetail { message: string } const handleEvent = (event: Event) => { const customEvent = event as CustomEvent<CustomEventDetail> console.log(customEvent.detail.message) }
2.10 常见问题解决
问题1:组件上的原生事件不生效
<!-- 子组件声明要触发的事件 -->
<script setup>
defineEmits(['click'])
</script>
<!-- 父组件显式监听 -->
<ChildComponent @click="handleClick" />
问题2:需要访问原生事件对象
<!-- 使用 $event 传递 -->
<button @click="handleClick($event)">按钮</button>
<script setup>
const handleClick = (event: MouseEvent) => {
console.log(event.clientX)
}
</script>