Vue2通过$emit触发事件,父组件使用@监听;
Vue3引入defineEmits()和emits选项显式声明事件,支持TypeScript类型检查。
主要差异:Vue3支持多v-model、移除.sync修饰符、提供事件验证,组合式API更灵活。
迁移时需注意移除$listeners、native修饰符,改用v-model:propName替代.sync。
Vue3事件系统更明确、类型安全,推荐使用defineEmits()声明事件。
Vue2 与 Vue3 自定义事件实现对比
一、Vue2 中的自定义事件
1. 子组件触发事件
vue
<template> <button @click="emitCustomEvent">触发事件</button> </template> <script> export default { methods: { emitCustomEvent() { // 触发自定义事件 this.$emit('custom-event', { message: '来自子组件的数据', timestamp: Date.now() }); // 触发带修饰符的事件 this.$emit('update:title', '新标题'); } } } </script>2. 父组件监听事件
vue
<template> <div> <child-component @custom-event="handleCustomEvent" @update:title="title = $event" /> <p>收到消息: {{ message }}</p> <p>标题: {{ title }}</p> </div> </template> <script> import ChildComponent from './ChildComponent.vue'; export default { components: { ChildComponent }, data() { return { message: '', title: '原始标题' }; }, methods: { handleCustomEvent(payload) { this.message = payload.message; console.log('收到事件:', payload); } } } </script>3. 使用 v-model 语法糖
vue
<!-- 子组件 --> <script> export default { model: { prop: 'value', event: 'change' }, props: ['value'], methods: { updateValue(newValue) { this.$emit('change', newValue); } } } </script> <!-- 父组件 --> <child-component v-model="dataValue" />二、Vue3 中的自定义事件
1. Composition API 方式
vue
<template> <button @click="emitEvent">触发事件</button> </template> <script setup> import { defineEmits } from 'vue'; // 定义可触发的事件 const emit = defineEmits([ 'custom-event', 'update:title', 'change' // 用于 v-model ]); const emitEvent = () => { // 触发事件 emit('custom-event', { message: '来自子组件的数据', timestamp: Date.now() }); // 触发带修饰符的事件 emit('update:title', '新标题'); }; </script> <!-- 或者使用类型声明 --> <script setup lang="ts"> const emit = defineEmits<{ (e: 'custom-event', payload: { message: string, timestamp: number }): void (e: 'update:title', value: string): void (e: 'change', value: any): void }>(); </script>2. Options API 方式
vue
<template> <button @click="emitEvent">触发事件</button> </template> <script> export default { emits: ['custom-event', 'update:title', 'change'], // 显式声明事件 // 或带验证 emits: { 'custom-event': (payload) => { return payload && typeof payload.message === 'string'; } }, methods: { emitEvent() { this.$emit('custom-event', { message: '数据', timestamp: Date.now() }); } } } </script>3. 父组件监听事件
vue
<template> <div> <ChildComponent @custom-event="handleCustomEvent" @update:title="title = $event" v-model:title="title" <!-- Vue3 支持多个 v-model --> /> <p>收到消息: {{ message }}</p> <p>标题: {{ title }}</p> </div> </template> <script setup> import { ref } from 'vue'; import ChildComponent from './ChildComponent.vue'; const message = ref(''); const title = ref('原始标题'); const handleCustomEvent = (payload) => { message.value = payload.message; console.log('收到事件:', payload); }; </script>三、主要差异对比
| 特性 | Vue2 | Vue3 |
|---|---|---|
| 事件定义 | 不需要显式声明 | emits选项或defineEmits() |
| 语法糖 | 单个v-model | 多个v-model绑定 |
| 修饰符 | .sync修饰符 | 内置v-model支持参数 |
| 组合式API | 不支持 | defineEmits() |
| 类型支持 | 有限 | 完整的 TypeScript 支持 |
| 事件验证 | 无内置验证 | 支持事件参数验证 |
四、最佳实践示例
Vue3 完整示例
vue
<!-- CustomButton.vue --> <template> <button @click="handleClick"> <slot>点击我</slot> </button> </template> <script setup lang="ts"> interface EmitEvents { (e: 'click', event: MouseEvent): void (e: 'custom-click', payload: { id: number, value: string }): void (e: 'update:modelValue', value: boolean): void } const emit = defineEmits<EmitEvents>(); const handleClick = (event: MouseEvent) => { // 触发原生事件 emit('click', event); // 触发自定义事件 emit('custom-click', { id: 1, value: '按钮被点击' }); // 触发 v-model 更新 emit('update:modelValue', true); }; </script> <!-- 使用组件 --> <template> <CustomButton v-model="isActive" @custom-click="handleCustomClick" @click="handleNativeClick" /> </template>事件验证示例
vue
<script> export default { emits: { // 验证提交事件 submit: (payload) => { // 必须返回布尔值表示验证是否通过 return ( payload && typeof payload.email === 'string' && payload.email.includes('@') && typeof payload.password === 'string' && payload.password.length >= 6 ); } }, methods: { handleSubmit() { const payload = { email: this.email, password: this.password }; // 验证失败会在控制台警告 this.$emit('submit', payload); } } } </script>五、迁移注意事项
.sync修饰符:Vue3 中已移除,使用v-model:propName替代$listeners:Vue3 中已移除,监听器直接作为$attrs的一部分事件名大小写:Vue3 中推荐使用 kebab-case,但 camelCase 也能工作
native 修饰符:Vue3 中已移除,所有事件都通过
emits定义
Vue3 的事件系统更加明确和类型安全,推荐总是使用emits选项或defineEmits()来声明组件可以触发的事件。