一、什么是自定义事件?
自定义事件 是 Vue 提供的一种机制,允许子组件通过
$emit触发一个事件,父组件通过v-on(即@)监听该事件并执行回调。
它实现了 "子 → 父" 的数据传递,是 单向数据流 的重要补充。
核心 API
$emit(eventName, ...args):在子组件中触发事件@event="handler":在父组件中监听事件
二、基本使用示例
1. 子组件触发事件
html
<!-- ChildButton.vue -->
<template>
<button @click="handleClick">
点我触发事件
</button>
</template>
<script setup>
// Vue 3: 使用 defineEmits 定义可触发的事件
const emit = defineEmits(['click', 'update'])
function handleClick() {
// 触发 'click' 事件,可携带参数
emit('click', { id: 1, name: '按钮1' })
// 也可触发多个事件
emit('update', Date.now())
}
</script>
✅
defineEmits是 Vue 3<script setup>中的宏,用于声明组件可触发的事件。
2. 父组件监听事件
html
<!-- ParentComponent.vue -->
<script setup>
import ChildButton from './ChildButton.vue'
// 定义事件处理函数
function onChildClick(data) {
console.log('子组件点击了:', data)
}
function onChildUpdate(timestamp) {
console.log('子组件更新时间:', timestamp)
}
</script>
<template>
<div>
<h2>父组件</h2>
<!-- 使用 v-on 监听子组件事件 -->
<ChildButton
@click="onChildClick"
@update="onChildUpdate"
/>
</div>
</template>
✅ 当子组件点击时,父组件的
onChildClick函数会被调用。
三、Vue 2 vs Vue 3 写法对比
| 场景 | Vue 2 写法 | Vue 3 <script setup> 写法 |
|---|---|---|
| 触发事件 | this.$emit('event', data) |
emit('event', data) |
| 定义事件 | (可选)emits: ['event'] |
const emit = defineEmits(['event']) |
| 访问 emit | this.$emit |
通过 defineEmits 返回 |
Vue 2 示例
javascript
// ChildComponent.vue
export default {
methods: {
handleClick() {
this.$emit('click', 'Hello from child')
}
}
}
html
<!-- Parent.vue -->
<ChildComponent @click="handleClick" />
✅ Vue 3 的写法更简洁、类型友好。
四、高级用法与最佳实践
1. 事件命名规范
- 使用 kebab-case (短横线命名):
@update-user、@item-click - 避免使用原生 DOM 事件名(如
click、input),除非你确实要覆盖
✅ 推荐:
@update:modelValue、@close-modal
2. v-model 的事件实现原理
v-model 本质上是 :modelValue + @update:modelValue 的语法糖。
子组件(可编辑输入框)
html
<!-- CustomInput.vue -->
<script setup>
const emit = defineEmits(['update:modelValue'])
defineProps(['modelValue'])
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input
:value="modelValue"
@input="onInput"
type="text"
/>
</template>
父组件使用 v-model
html
<script setup>
import CustomInput from './CustomInput.vue'
import { ref } from 'vue'
const inputValue = ref('')
</script>
<template>
<!-- v-model 自动绑定 modelValue 和 update:modelValue -->
<CustomInput v-model="inputValue" />
<p>输入内容:{{ inputValue }}</p>
</template>
✅ 这就是
v-model的工作原理!
3. 使用 defineEmits 进行类型校验(TypeScript)
html
<script setup lang="ts">
// 定义事件类型
const emit = defineEmits<{
(e: 'add', id: number): void
(e: 'delete', id: number, reason: string): void
(e: 'change', value: string): void
}>()
function handleAdd() {
emit('add', 123) // 类型安全 ✅
}
</script>
✅ TypeScript 下,IDE 会自动提示事件名和参数类型。
4. 事件修饰符
Vue 支持事件修饰符,也可用于自定义事件:
html
<!-- .once:只监听一次 -->
<ChildComponent @click.once="handleClick" />
<!-- .stop:阻止事件冒泡(较少用) -->
<ChildComponent @click.stop="handleClick" />
五、常见误区与解决方案
❌ 误区 1:在 <script setup> 中使用 this.$emit
javascript
// ❌ 错误!<script setup> 中没有 this
this.$emit('click')
// ✅ 正确:使用 defineEmits
const emit = defineEmits(['click'])
emit('click')
❌ 误区 2:事件名使用 camelCase
html
<!-- ❌ 不推荐 -->
<ChildComponent @itemClick="handle" />
<!-- ✅ 推荐 -->
<ChildComponent @item-click="handle" />
HTML 属性不区分大小写,建议统一使用 kebab-case。
❌ 误区 3:子组件直接修改 props
javascript
// ❌ 错误!不要这样做
props.modelValue = 'new value'
// ✅ 正确:通过事件通知父组件
emit('update:modelValue', 'new value')
✅ 遵循单向数据流原则。
六、自定义事件 vs $attrs vs ref
| 通信方式 | 适用场景 | 方向 | 是否推荐 |
|---|---|---|---|
| 自定义事件 | 子 → 父 通知 | 子 → 父 | ✅ 推荐 |
$attrs |
透传属性和事件 | 父 → 子 | ✅ 适合高阶组件 |
ref |
父 → 子 调用方法 | 父 → 子 | ⚠️ 谨慎使用 |
📌 通信原则:
- 数据流:
props向下,events向上- 避免过度使用
ref调用子组件方法
七、总结
| 核心点 | 说明 |
|---|---|
| 作用 | 实现子组件向父组件通信 |
| API | $emit / defineEmits |
| 语法 | @event="handler" |
| Vue 3 | defineEmits 更类型安全 |
v-model |
基于 update:modelValue 事件 |
| 最佳实践 | 使用 kebab-case、避免修改 props |
📌 一句话总结 :
自定义事件是 Vue 组件通信的"标准语言",掌握它,你才能构建可维护的组件体系。
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!