Vue中的props和emit

Vue 组件通信中 props 与 emit 的职责分工和单向数据流边界。

#tech / dev / frame #resource / vue3 #type / concept #status / growing

[!info] related notes

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 会在开发模式下发出警告。如果需要本地副本,用 toRefcomputed
  • 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> 中使用:且必须在顶层调用,不能在函数内部。

这篇和”定义方法总结”的边界

这篇只讲职责和数据流边界;如果你要看 definePropswithDefaultsdefineEmits 的写法细节,回到 props 与 emit 定义

和通信总览的关系

如果你想把它放回整张组件通信地图里,继续看 vue-component-communicationvue-component-communication-selection.

创建于 2026/3/19 更新于 2026/5/27