Props与Emits对比摘要
核心区别:Props实现父→子单向数据流(用于配置),Emits实现子→父事件通知(用于交互)。
特性对比:
- 数据流:Props向下传递只读数据,Emits向上触发事件
- 验证:Props支持类型/默认值验证,Emits无内置验证
- Vue3优化:均支持TS类型推断,Composition API提供defineProps/defineEmits
使用场景:
- Props:初始化配置/展示数据
- Emits:用户交互反馈/状态变更通知
最佳实践:
- 遵循单向数据流原则
- 为复杂数据添加验证
- 使用kebab-case命名事件
- 避免直接修改Props
特殊用法:v-model本质是Props+Emits的组合语法糖。
Props 和 Emits 对比总结
| 对比维度 | Props(属性) | Emits(事件) |
|---|---|---|
| 数据流向 | 父 → 子(单向向下) | 子 → 父(单向向上) |
| 作用 | 父组件向子组件传递数据 | 子组件向父组件通知事件 |
| 使用场景 | 配置子组件、传递初始数据 | 用户交互反馈、状态变化通知 |
| 是否可变 | 只读(子组件不应直接修改) | 可触发多次,传递不同数据 |
| 响应式 | 在子组件中是响应式的 | 事件本身不是响应式的,但可传递响应式数据 |
Vue 2 Options API 中的用法
| 特性 | Props | Emits |
|---|---|---|
| 声明方式 | props: ['title', 'content'] 或对象形式 |
emits: ['submit', 'change'](Vue 2.3+) |
| 类型验证 | 支持完整类型验证 | 无内置验证(Vue 2 中) |
| 默认值 | 通过 default 属性设置 |
不适用 |
| 必需性 | 通过 required: true 设置 |
不适用 |
| 代码示例 | props: {<br> title: {<br> type: String,<br> required: true<br> }<br>} |
emits: ['update:modelValue'] |
Vue 3 Composition API / <script setup> 中的用法
| 特性 | Props | Emits |
|---|---|---|
| 声明方式 | defineProps() |
defineEmits() |
| 类型验证 | 支持对象和泛型两种方式 | 支持数组和对象两种方式 |
| TypeScript | 完美支持类型推断 | 完美支持类型推断 |
| 访问方式 | 通过 props.xxx 访问 |
通过 emit('event', data) 触发 |
| 代码示例 | const props = defineProps<{<br> title: string,<br> count?: number<br>}>() |
const emit = defineEmits<{<br> (e: 'submit', data: any): void<br>}>() |
实际使用示例对比
Props 示例
javascript
<!-- 父组件 Parent.vue -->
<template>
<!-- 传递 props -->
<ChildComponent :title="pageTitle" :count="counter" />
</template>
<!-- 子组件 Child.vue -->
<template>
<h1>{{ title }}</h1>
<p>计数: {{ count }}</p>
</template>
<script setup>
// Vue 3 Composition API
const props = defineProps({
title: String,
count: {
type: Number,
default: 0
}
})
</script>
Emits 示例
vue
javascript
<!-- 子组件 Child.vue -->
<template>
<button @click="handleClick">提交</button>
</template>
<script setup>
// Vue 3 Composition API
const emit = defineEmits(['submit', 'update:value'])
const handleClick = () => {
// 触发事件并传递数据
emit('submit', { id: 1, data: 'test' })
emit('update:value', 'new value')
}
</script>
<!-- 父组件 Parent.vue -->
<template>
<!-- 监听事件 -->
<ChildComponent
@submit="handleSubmit"
@update:value="value = $event"
/>
</template>
特殊用法:双向绑定
| 模式 | 实现方式 | 说明 |
|---|---|---|
| v-model | props: modelValue + emits: update:modelValue |
Vue 3 默认方式 |
| v-model:propName | props: propName + emits: update:propName |
Vue 3 参数化 v-model |
| .sync 修饰符 | props: xxx + emits: update:xxx |
Vue 2 方式(Vue 3 已废弃) |
双向绑定示例
vue
javascript
<!-- 子组件 CustomInput.vue -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const updateValue = (event) => {
emit('update:modelValue', event.target.value)
}
</script>
<template>
<input :value="modelValue" @input="updateValue" />
</template>
<!-- 父组件使用 -->
<template>
<!-- Vue 3 默认 v-model -->
<CustomInput v-model="username" />
<!-- Vue 3 参数化 v-model -->
<CustomInput v-model:firstName="first" v-model:lastName="last" />
</template>
最佳实践对比
| 最佳实践 | Props | Emits |
|---|---|---|
| 命名规范 | 使用 camelCase 声明,kebab-case 传递 | 使用 kebab-case 命名事件 |
| 数据验证 | 始终添加类型验证和默认值 | 为复杂数据添加验证函数 |
| 单向数据流 | 不要直接修改 props,使用 emits 通知父组件修改 | 传递最小必要数据,避免传递整个对象 |
| TypeScript | 使用泛型语法获得更好的类型安全 | 为事件定义完整类型签名 |
| 文档注释 | 为每个 prop 添加 JSDoc 注释说明用途 | 为每个事件添加注释说明触发时机 |
常见错误对比
| 错误类型 | Props 相关错误 | Emits 相关错误 |
|---|---|---|
| 语法错误 | 使用 v-bind 传递动态值::prop="value" |
监听事件使用 @ 或 v-on:@event="handler" |
| 逻辑错误 | 在子组件中修改 props(违反单向数据流) | 在父组件中直接修改子组件数据(应通过事件) |
| 性能问题 | 传递大型对象导致不必要的重新渲染 | 频繁触发高开销的事件处理函数 |
| 类型错误 | 传递错误类型的数据(类型验证可捕获) | 事件参数类型与处理函数期望类型不匹配 |
选择指南
| 场景 | 应该使用 Props | 应该使用 Emits |
|---|---|---|
| 配置子组件 | ✅ 传递初始配置、样式参数 | ❌ |
| 用户交互反馈 | ❌ | ✅ 按钮点击、表单提交 |
| 数据展示 | ✅ 传递要显示的数据 | ❌ |
| 状态变化通知 | ❌ | ✅ 数据更新、组件状态变化 |
| 表单输入 | ✅ 接收初始值 | ✅ 输入变化时通知父组件 |
核心原则:Props 向下,Events 向上。
父组件通过 Props 控制子组件的显示,子组件通过 Emits 通知父组件用户交互。