Vue中的props和emit
Vue 组件通信中 props 与 emit 的职责分工和单向数据流边界。
#tech / dev / frame
#resource / vue3
#type / concept
#status / growing
[!info] related notes
- 所属 MOC: Vue MOC
- 上位主题: vue3
- 相关主题: vue3-slots, Vue Composition API, vue-component-communication, vue-component-communication-selection, vue-define-model, vue-v-model
- 代码写法: props 与 emit 定义
Vue中的props和emit
一句话定义
props:父组件传给子组件的数据输入emit:子组件通知父组件发生了什么事
核心机制
defineProps 基本用法
<!-- 子组件 Child.vue -->
<script setup>
// 基础声明
const props = defineProps({
title: String,
count: { type: Number, default: 0 },
items: { type: Array, required: true },
config: {
type: Object,
default: () => ({})
}
})
// 使用 props
console.log(props.title)
</script>
<template>
<h2>{{ title }}</h2>
<p>计数: {{ count }}</p>
</template>
TypeScript 类型声明
<script setup lang="ts">
// 基于类型的声明
interface Props {
title: string
count?: number
items: string[]
status: 'idle' | 'loading' | 'error'
}
const props = defineProps<Props>()
// 带默认值
const propsWithDefaults = withDefaults(defineProps<Props>(), {
count: 0,
status: 'idle'
})
</script>
defineEmits 基本用法
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['submit', 'cancel', 'update'])
function handleClick() {
emit('submit', { id: 1, name: 'foo' })
}
</script>
<template>
<button @click="handleClick">提交</button>
<button @click="emit('cancel')">取消</button>
</template>
TypeScript 类型声明的 emit
<script setup lang="ts">
const emit = defineEmits<{
(e: 'submit', payload: { id: number; name: string }): void
(e: 'cancel'): void
(e: 'update', value: string): void
}>()
</script>
或者 Vue 3.3+ 的简写:
<script setup lang="ts">
const emit = defineEmits<{
submit: [payload: { id: number; name: string }]
cancel: []
update: [value: string]
}>()
</script>
父组件使用
<!-- 父组件 -->
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const items = ref(['a', 'b', 'c'])
function handleSubmit(data) {
console.log('收到子组件提交:', data)
}
</script>
<template>
<Child
title="列表"
:count="items.length"
:items="items"
@submit="handleSubmit"
@cancel="console.log('取消')"
/>
</template>
单向数据流
<script setup>
// 错误示范:直接修改 props
const props = defineProps({ count: Number })
function increment() {
// props.count++ // 不要这样做!
// 正确做法:通过 emit 通知父组件
emit('update:count', props.count + 1)
}
</script>
如果确实需要在子组件内部维护一份可修改的副本:
<script setup>
import { toRef, computed } from 'vue'
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])
// 方案 1:computed + emit
const localValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
// 方案 2:使用 defineModel(Vue 3.4+)
const model = defineModel()
</script>
边界与常见误解
- 不要直接修改 props:Vue 会在开发模式下发出警告。如果需要本地副本,用
toRef或computed。 - props 是单向数据流:父组件更新 props,子组件自动接收;子组件不能反向修改。
- emit 不传数据也能用:有些事件只需要通知,不需要携带数据。
- camelCase vs kebab-case:JS 中用
defineProps({ myProp: String }),模板中用:my-prop="value",Vue 自动转换。 - props 类型验证只在开发模式下生效:生产环境中不会检查类型。
- emit 的事件名:Vue 3 推荐 camelCase 命名(
updateValue),模板中用 kebab-case(@update-value)。 - defineProps / defineEmits 只能在
<script setup>中使用:且必须在顶层调用,不能在函数内部。
这篇和”定义方法总结”的边界
这篇只讲职责和数据流边界;如果你要看 defineProps、withDefaults、defineEmits 的写法细节,回到 props 与 emit 定义。
和通信总览的关系
如果你想把它放回整张组件通信地图里,继续看 vue-component-communication 和 vue-component-communication-selection.