(1)Props ------ 父传子(单向、只读)
是什么 :父组件把数据通过属性传给子组件。子组件通过 props 读取。
什么时候用:父组件要把显示数据或配置传给子组件时(比如标题、初始值、只读配置)。
要点/注意:
-
Props 是 单向(父 → 子),子不能直接修改父传下来的 props(在子里 props 是只读的)。
-
如果子要"改变"显示值,用本地
ref/computed拷贝一份,或通过emit通知父改变数据(见下)。 -
在 TypeScript 的
script setup中用defineProps<T>()声明类型(你示例正确)。
示例(父传初始值,子显示):
javascript
<!-- 父 -->
<ChildComponent :message="parentMessage" />
<!-- 子 -->
<script setup lang="ts">
interface Props { message: string }
const props = defineProps<Props>()
</script>
<template>
<div>{{ props.message }}</div>
</template>
快捷提示:如果你需要在子里编辑父的数据,不要直接修改 props,而是 emit 一个事件让父去改。
(2)Emits ------ 子传父(事件机制)
是什么 :子组件用 emit 向父发送事件(带数据),父在模板上通过**@事件名** 监听并处理。
什么时候用:子组件触发行为需要父响应或改变父的数据(例如:子里的按钮点了要通知父更新列表、输入框更新等)。
要点/注意:
-
在
script setup里用const emit = defineEmits<...>()做类型校验(你的示例也很好)。 -
事件名在模板里通常用 kebab-case(
@update或@update-value),在 JS/TS 中可以用 camelCase。 -
常见约定:父负责数据,子负责 UI/事件 -> "props down, events up"。
示例(子告诉父更新值):
javascript
<!-- 子 -->
<script setup lang="ts">
const emit = defineEmits<{ (e: 'update', value: string): void }>()
const handleClick = () => emit('update', 'new value')
</script>
<!-- 父 -->
<ChildComponent @update="handleUpdate" />
v-model 快捷约定 :对表单组件常用 v-model。实现时子接收 modelValue(prop)并 emit update:modelValue 事件:
javascript
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits(['update:modelValue'])
// 子在输入时:
emit('update:modelValue', newVal)
(3)Provide / Inject ------ 跨层级传值(祖先 → 后代)
是什么 :祖先组件 provide 一个值,任意深度的后代 inject 获取该值,不需要逐层传 props。
什么时候用:当数据要被很多深层子组件共享,但不想层层透传 props(比如主题、语言、表单上下文、库内部共享状态)。
要点/注意:
-
默认不是响应式 :如果你
provide('key', 123),之后改变祖先里的普通变量不会自动通知后代。要响应式传递请provide('k', ref(...) )或provide('k', reactive(...))。 -
inject可以带默认值:const theme = inject('theme', 'light')。 -
不要把 provide/inject 当成全局状态管理(复杂逻辑、多个组件频繁读写时用 Pinia/Vuex 更合适)。
示例(响应式主题):
javascript
// 祖先
import { ref, provide } from 'vue'
const theme = ref('dark')
provide('theme', theme) // 提供 ref
// 后代
import { inject } from 'vue'
const theme = inject('theme', ref('light')) // theme 是一个 ref,可以直接解构使用
(4)小贴士 & 最佳实践(干货)
-
首选规则:Props(向下) + Emits(向上)。这是最清晰的组件边界与职责分配方式。
-
不要修改 props :若需要本地编辑,用
const local = ref(props.value)或computed({ get: ()=>props.x, set: v => emit('update', v) })。 -
命名一致性 :事件名用语义化如
save,delete,update:modelValue;props 用清晰名如items,visible。 -
Provide/Inject 的使用门槛:适合库级别或跨很多层的共享,不适合替代父子通信或做频繁的读写共享(那用 Pinia)。
-
TypeScript :
defineProps<T>()和defineEmits<...>()能把类型错误在编译时暴露出来,推荐始终加类型。 -
事件大小写 :模板中监听事件最好写 kebab-case(
@update-value),传递/emit 时在 JS 可写 camelCase。这样兼容模板解析。