在 Vue3 开发中,父子组件通信是最基础也最核心的技能,不管是简单页面还是复杂项目,都离不开父子组件的数据传递与事件交互。
这篇博客会用最通俗的语言,结合 <script setup> 语法(Vue3 推荐写法),把6种常用父子组件通信方案讲透,从基础到进阶,看完就能直接上手用!
一、先搞懂:什么是父子组件?
简单说:在父组件中引入并使用的组件,就是子组件。
比如:父组件 Father.vue 里写了 <Child />,那 Father 就是父组件,Child 就是子组件。
通信核心场景:
- 父组件 → 子组件:传数据、传方法
- 子组件 → 父组件:触发事件、回传数据
二、Vue3 父子组件通信 6 大方案(全实战)
1. props / defineProps(父 → 子 传数据)
最基础、最常用:父组件通过属性传值,子组件用 defineProps 接收(只读,不能直接修改)。
父组件 Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<!-- 向子组件传值:msg、list、user -->
<Child
msg="我是父组件传的字符串"
:list="['Vue3', 'React', 'Angular']"
:user="{ name: '张三', age: 20 }"
/>
</div>
</template>
<script setup>
// 引入子组件
import Child from './Child.vue'
</script>
子组件 Child.vue
<template>
<div class="child">
<h4>子组件</h4>
<p>字符串:{{ msg }}</p>
<p>数组:{{ list[0] }}</p>
<p>对象:{{ user.name }}</p>
</div>
</template>
<script setup>
// 1. defineProps 接收父组件传的值(Vue3 <script setup> 专属)
// 2. 支持类型校验、默认值、必传校验
const props = defineProps({
// 基础类型校验
msg: String,
// 复杂类型 + 默认值
list: {
type: Array,
default: () => []
},
// 必传参数
user: {
type: Object,
required: true
}
})
// 直接使用 props.xxx 访问数据
console.log(props.msg)
</script>
核心要点:
- props 是只读的,子组件不能直接修改 props(会报警告)
- 支持类型校验、默认值、必传限制,代码更健壮
2. defineEmits(子 → 父 传数据/触发事件)
子组件不能直接修改父组件数据,需要触发自定义事件,把数据回传给父组件。
子组件 Child.vue(触发事件)
<template>
<button @click="sendToFather">点击给父组件传值</button>
</template>
<script setup>
// 1. 定义要触发的事件名(和自定义事件名一致)
const emit = defineEmits(['child-event'])
const sendToFather = () => {
// 2. 触发事件:emit(事件名, 传递的数据1, 数据2...)
emit('child-event', '我是子组件回传的消息', 666)
}
</script>
父组件 Father.vue(监听事件)
<template>
<Child @child-event="handleChild" />
</template>
<script setup>
import Child from './Child.vue'
// 接收子组件回传的数据
const handleChild = (msg, num) => {
console.log('父组件收到:', msg, num) // 我是子组件回传的消息 666
}
</script>
核心要点:
- 子组件只负责触发事件+传值,逻辑交给父组件处理
- 遵循 Vue 单向数据流 原则,代码更易维护
3. ref / defineExpose(父 → 子 调用方法/访问数据)
父组件想直接调用子组件的方法、获取子组件数据,用 ref + defineExpose。
子组件 Child.vue(暴露方法/数据)
<script setup>
import { ref } from 'vue'
// 子组件数据
const childMsg = ref('我是子组件数据')
// 子组件方法
const childFn = () => {
alert('子组件方法被父组件调用了!')
}
// 关键:必须用 defineExpose 暴露,父组件才能访问
defineExpose({
childMsg,
childFn
})
</script>
父组件 Father.vue(调用子组件)
<template>
<Child ref="childRef" />
<button @click="callChild">父组件调用子组件</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
// 1. 创建 ref,名字和子组件上的 ref 一致
const childRef = ref(null)
const callChild = () => {
// 2. 通过 childRef.value 访问子组件暴露的内容
childRef.value.childFn() // 调用子组件方法
console.log(childRef.value.childMsg) // 获取子组件数据
}
</script>
核心要点:
- 子组件必须用
defineExpose主动暴露,父组件才能获取 - 适合:父组件主动控制子组件(如清空表单、重置状态)
4. v-model / defineModel(双向绑定:父子同步数据)
Vue3.4+ 新增 defineModel,一行代码实现父子数据双向同步,比传统 v-model 更简洁!
子组件 Child.vue(defineModel)
<template>
<!-- 直接绑定 modelValue,修改会自动同步到父组件 -->
<input v-model="modelValue" placeholder="输入同步到父组件" />
</template>
<script setup>
// 一行代码搞定双向绑定,无需 defineProps + defineEmits
const modelValue = defineModel()
</script>
父组件 Father.vue(v-model)
<template>
<p>父组件数据:{{ inputVal }}</p>
<!-- v-model 绑定,子组件修改会自动更新这里 -->
<Child v-model="inputVal" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const inputVal = ref('')
</script>
核心要点:
- 适合表单、开关等需要双向同步的场景
- Vue3.4+ 才能用
defineModel,极简写法首选
5. provide / inject(父 → 后代 跨级传值)
如果父组件有多层子组件(父→子→孙),用 props 太麻烦,直接用 provide 传值,后代用 inject 接收。
父组件 Father.vue(提供数据)
<script setup>
import { ref, provide } from 'vue'
import Child from './Child.vue'
const fatherData = ref('我是父组件跨级传的值')
// provide(键名, 数据)
provide('FATER_DATA', fatherData)
</script>
孙组件 GrandSon.vue(接收数据)
<template>
<p>孙组件接收:{{ data }}</p>
</template>
<script setup>
import { inject } from 'vue'
// inject(键名) 直接接收
const data = inject('FATER_DATA')
</script>
核心要点:
- 无视组件层级,后代组件都能接收
- 适合:主题、用户信息等全局共享数据
6. 父传子:传递方法(父 → 子 传函数)
父组件直接把自己的方法通过 props 传给子组件,子组件直接调用。
父组件 Father.vue
<script setup>
import Child from './Child.vue'
// 父组件方法
const fatherFn = (val) => {
alert('父组件方法被调用,值:' + val)
}
</script>
<template>
<!-- 把方法传给子组件 -->
<Child :father-fn="fatherFn" />
</template>
子组件 Child.vue
<script setup>
// 接收方法
const props = defineProps({
fatherFn: Function
})
// 直接调用
const useFatherFn = () => {
props.fatherFn('子组件调用')
}
</script>
核心要点:
- 简单直接,但不推荐频繁使用(违背单向数据流)
- 适合简单场景,复杂场景优先用
emit
三、方案选型:该用哪个?
| 场景 | 推荐方案 | 优先级 |
|---|---|---|
| 父→子 传普通数据 | props / defineProps | 最高 |
| 子→父 回传数据 | defineEmits | 最高 |
| 父调用子方法/数据 | ref + defineExpose | 中 |
| 父子数据双向同步 | v-model / defineModel | 高 |
| 父→后代 跨级传值 | provide / inject | 中 |
| 简单传函数 | props 传方法 | 低 |
四、总结
Vue3 父子组件通信核心就 3 句话:
- 父传子:用
props(基础)、v-model(双向)、provide(跨级) - 子传父:用
defineEmits触发事件 - 父调子:用
ref+defineExpose
所有方案都基于 <script setup> 写法,是 Vue3 最新、最简洁的实践,日常开发掌握前 4 种就足够应对 90% 场景!
总结
- 父子组件通信遵循单向数据流,子组件不直接修改父组件数据
- 基础场景用
props + emit,双向绑定用defineModel,跨级用provide/inject - 父调用子必须搭配
defineExpose,这是 Vue3<script setup>的强制规则
我可以帮你把这篇博客整理成可直接发布的Markdown文件,还能补充代码高亮、目录和配图说明,需要吗?