Vue2通过$emit触发事件,父组件使用@监听;
Vue3引入defineEmits()和emits选项显式声明事件,支持TypeScript类型检查。
主要差异:Vue3支持多v-model、移除.sync修饰符、提供事件验证,组合式API更灵活。
迁移时需注意移除$listeners、native修饰符,改用v-model:propName替代.sync。
Vue3事件系统更明确、类型安全,推荐使用defineEmits()声明事件。
Vue2 与 Vue3 自定义事件实现对比
一、Vue2 中的自定义事件
1. 子组件触发事件
vue
javascript
<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
javascript
<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
javascript
<!-- 子组件 -->
<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
javascript
<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
javascript
<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
javascript
<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
javascript
<!-- 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
javascript
<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() 来声明组件可以触发的事件。