人人都会AI编程

Emits 向上触发事件:自定义事件、参数传递

更新时间:2026-07-11

在 Vue 的组件通信模型中,Props 负责父组件向子组件传递数据,而 Emits 负责子组件向父组件发送消息。它遵循单向数据流原则:子组件不能直接修改父组件传入的 Props,但可以通过触发自定义事件,让父组件自己决定如何响应变化。

子组件:声明与触发事件

Vue 3 推荐使用 <script setup> 语法糖与 defineEmits 宏来声明组件可触发的事件。这既是给父组件的“使用说明书”,也能在编辑器中提供类型提示。

<!-- ChildCounter.vue -->
<template>
  <div>
    <p>当前计数:{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

<script setup>
// 1. 定义组件的 props
const props = defineProps({
  count: Number
})

// 2. 声明可触发的事件及其参数类型
const emit = defineEmits(['update:count'])

// 3. 触发事件,向上传递新值
function increment() {
  emit('update:count', props.count + 1)
}
</script>

defineEmits 接收一个字符串数组,列出所有可用的事件名。调用 emit('事件名', 参数1, 参数2, ...) 即可通知父组件,后续参数都会被传递给父组件的监听函数。

父组件:监听事件并处理

父组件在模板中使用 v-on(简写 @)来监听子组件抛出的自定义事件,就像监听原生 DOM 事件一样。

<!-- ParentPage.vue -->
<template>
  <ChildCounter :count="counter" @update:count="handleCountUpdate" />
</template>

<script setup>
import { ref } from 'vue'
import ChildCounter from './ChildCounter.vue'

const counter = ref(0)

function handleCountUpdate(newValue) {
  counter.value = newValue   // 父组件负责修改数据,再通过 props 传回子组件
}
</script>

事件处理函数接收子组件传递的参数(这里是 newValue),然后更新父组件自身的状态。这保证了数据的所有权始终在父组件,子组件只是“建议”父组件做出更新,符合单向数据流的清晰结构。

带参数的复杂场景

事件可以携带多个参数,比如一个表单组件在提交时传递整个表单对象:

<!-- ChildForm.vue -->
<template>
  <form @submit.prevent="submit">
    <input v-model="name" />
    <input v-model="email" />
    <button type="submit">提交</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'

const name = ref('')
const email = ref('')
const emit = defineEmits(['submit-form'])

function submit() {
  emit('submit-form', { name: name.value, email: email.value })
}
</script>

父组件监听时可以直接解构参数:

<ChildForm @submit-form="handleFormSubmit" />
function handleFormSubmit(formData) {
  console.log('收到表单:', formData.name, formData.email)
}

TypeScript 支持:给事件加类型

使用 TypeScript 时,defineEmits 可以接收类型参数,为事件及其负载提供严格的类型校验:

const emit = defineEmits<{
  (e: 'update:count', value: number): void
  (e: 'submit-form', data: { name: string; email: string }): void
}>()

这样在触发事件时,参数类型和数量会得到 IDE 提示和编译检查;父组件在监听事件时也能自动推断处理函数的参数类型。

实际开发中的常见模式

  1. v-model 的本质

稍后会详细讲到 v-model 是 Props + Emits 的语法糖。一个 <Child v-model:count="parentCount" /> 会被展开成 :count="parentCount" @update:count="parentCount = $event"。Emits 正是 update:xx 系列自定义事件的基础。

  1. “状态提升”的实现载体

当两个同级组件需要通信时,通常将共享状态提取到最近的公共父组件,然后通过 Props 下发给组件 A,通过 Emits 接收组件 B 的更新通知——Emits 是这种模式不可或缺的一环。

  1. 与原生事件区分

自定义事件名建议使用小写字母加连字符(如 submit-form),以避免与原生 DOM 事件冲突。Vue 3 移除了 v-on.native 修饰符,所有事件监听都需要显式声明;如需监听组件根元素的原生事件,可在子组件内通过 inheritAttrs: false + 手动绑定来暴露。

避开常见的坑

  • 不要在 emit 时修改 props

子组件中直接修改 props.xxx 会引发 Vue 警告,且无法让数据同步回父组件。正确的做法永远是 emit 一个新值,让父组件更新自己的数据后再通过 props 回传。

  • 事件名大小写

虽然 DOM 模板中事件名会被自动转为小写,但习惯上在 JavaScript 中定义事件时使用 camelCase(如 update:count),在模板中使用 kebab-case(@update:count)。Vue 会自动映射,所以保持一致即可。

  • 忘记声明 emit

如果不在 defineEmits 中声明事件,组件依然可以触发事件,但会触发控制台警告:“Component emitted event xxx but it is neither declared...”。声明不仅是规范,也是获得类型提示和未来兼容性检查的依据。

说到底,Emits 是一个简单但关键的“上行通道”,它让父子组件之间的数据流动变得可预测、可维护,也为我们后续讨论 v-model 原生支持铺平了道路。