Vue 3 defineEmits 和 emits 选项的区别

FreeGuideOnline 最新 2026-07-05

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 风格,但它们的目的完全相同——让组件的自定义事件清晰、可验证且类型安全。