Vue 3 defineEmits 和 emits 选项的区别
Vue 3 中 defineEmits 与 emits 选项的区别
Vue 3 提供了两种声明组件自定义事件的方式:emits 选项(用于选项式 API)与 defineEmits 编译宏(用于组合式 API 的 <script setup> 语法)。两者的核心用途相同——明确告知组件会触发哪些事件,并可选地添加运行时验证。但对于初学者,它们的使用场景和语法形式截然不同。
什么是 emits 选项?
emits 是选项式 API(Options API)中的一个组件选项,通过数组或对象的形式声明该组件可能向父组件触发的所有事件。
数组语法:以字符串数组列出事件名。
export default {
emits: ['update', 'delete'],
setup(props, { emit }) {
// 触发事件
emit('update')
}
}
对象语法:可对每个事件添加一个验证函数,接收传递给事件的参数,返回布尔值指示是否允许触发。
export default {
emits: {
// 未验证
update: null,
// 验证参数
delete: (payload) => {
return payload.id !== undefined
}
},
methods: {
handleDelete() {
this.$emit('delete', { id: 123 })
}
}
}
验证函数返回 false 时,事件仍然会被触发,但 Vue 会在控制台抛出警告,这对开发阶段的类型安全很有帮助。
什么是 defineEmits 宏?
defineEmits 是 <script setup> 语法中独有的编译宏。它同样用于声明事件,但无需从组件中导入,因为它是一个编译器处理的宏,最终会在编译时被移除。它既支持运行时声明,也支持纯类型声明(搭配 TypeScript)。
基本用法(数组或对象形式):
<script setup>
// 数组声明
const emit = defineEmits(['update', 'delete'])
// 对象声明(带验证)
const emit = defineEmits({
update: null,
delete: (payload) => {
return payload.id !== undefined
}
})
emit('update')
</script>
TypeScript 类型声明:
<script setup lang="ts">
const emit = defineEmits<{
(e: 'update'): void
(e: 'delete', id: number): void
}>()
</script>
这种方式提供了更精确的类型推断,是组合式 API + TypeScript 项目中的推荐写法。
核心区别对比
| 对比维度 | emits 选项 |
defineEmits 宏 |
|---|---|---|
| 所属 API 风格 | 选项式 API | <script setup> 组合式 API |
| 定义位置 | export default 内部 |
<script setup> 顶层 |
| 是否需要导入 | 否,是组件选项 | 否,是编译宏,无需导入 |
| 获取 emit 函数 | setup(props, { emit }) 或 this.$emit |
直接赋值 const emit = defineEmits(...) |
| 类型支持 | 可配合 JSDoc 或 TypeScript 声明类型,但稍显繁琐 | 原生支持 TypeScript 类型声明,体验更流畅 |
| 运行时验证 | 支持对象语法验证函数 | 同样支持对象语法验证函数 |
| 编译结果 | 保留在组件对象中 | 编译时会被完全移除,不会出现在运行时代码里 |
| 适用场景 | 旧项目迁移、习惯选项式 API、或未使用 <script setup> 时 |
新项目、组合式 API 项目、TypeScript 项目 |
什么时候用哪个?
- 使用
emits选项:如果你正在使用传统的选项式 API,或需要维护一个非<script setup>的老项目,这是唯一的选择。 - 使用
defineEmits宏:在现代 Vue 3 项目中,强烈推荐采用<script setup>语法。defineEmits更简洁,与 TypeScript 配合得天衣无缝,是当前官方推荐的事件声明方式。
注意:两者不能混用。如果你在 <script setup> 中又写了一个普通的 <script> 来定义 emits 选项,这会变得混乱且通常没必要。应该只选一种方式。
实际示例比较
选项式 API + emits 选项
<template>
<button @click="doUpdate">更新</button>
</template>
<script>
export default {
emits: ['update'],
methods: {
doUpdate() {
this.$emit('update', new Date())
}
}
}
</script>
组合式 API + defineEmits
<template>
<button @click="doUpdate">更新</button>
</template>
<script setup>
const emit = defineEmits(['update'])
function doUpdate() {
emit('update', new Date())
}
</script>
加入 TypeScript 的类型安全版本
<script setup lang="ts">
const emit = defineEmits<{
(e: 'update', timestamp: number): void
(e: 'delete', id: string): void
}>()
emit('update', Date.now())
emit('delete', 'abc123')
</script>
若尝试传入错误类型,编辑器会立刻报错。
常见疑问
Q:defineEmits 可以不在 <script setup> 中使用吗?
A:不可以。它是专门为 <script setup> 设计的编译宏,如果在普通 <script> 中使用会报错。
Q:如果不声明 emits,直接 emit 会怎样?
A:Vue 3 仍然支持不声明 emits 就触发事件,但这样会失去事件的文档化和验证能力;同时,组件上的事件监听器也会被 Vue 当作“透传 attribute”,可能导致原生事件意外触发。为了代码清晰和可维护性,强烈建议始终声明 emits。
Q:defineEmits 返回的 emit 函数能在异步函数里用吗?
A:可以,它和 setup 中的 emit 完全一样,可以在任何地方调用。
总结
emits选项用于选项式 API,定义在export default内,通过this.$emit调用。defineEmits是<script setup>中的编译宏,声明后直接获得类型安全的emit函数。- 两者都支持数组和对象(带验证)两种形式;
defineEmits额外支持纯 TypeScript 类型声明。 - 在当代 Vue 3 开发中,拥抱
<script setup>+defineEmits能提升开发体验和代码健壮性。
选择哪种方式取决于你使用的 API 风格,但它们的目的完全相同——让组件的自定义事件清晰、可验证且类型安全。