Vue3 父子组件通信全攻略:6种方案+实战案例

9 阅读5分钟

在 Vue3 开发中,父子组件通信是最基础也最核心的技能,不管是简单页面还是复杂项目,都离不开父子组件的数据传递与事件交互。

这篇博客会用最通俗的语言,结合 <script setup> 语法(Vue3 推荐写法),把6种常用父子组件通信方案讲透,从基础到进阶,看完就能直接上手用!

一、先搞懂:什么是父子组件?

简单说:在父组件中引入并使用的组件,就是子组件

比如:父组件 Father.vue 里写了 <Child />,那 Father 就是父组件,Child 就是子组件。

通信核心场景:

  1. 父组件 → 子组件:传数据、传方法
  2. 子组件 → 父组件:触发事件、回传数据

二、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 句话:

  1. 父传子:用 props(基础)、v-model(双向)、provide(跨级)
  2. 子传父:用 defineEmits 触发事件
  3. 父调子:用 ref + defineExpose

所有方案都基于 <script setup> 写法,是 Vue3 最新、最简洁的实践,日常开发掌握前 4 种就足够应对 90% 场景!


总结

  1. 父子组件通信遵循单向数据流,子组件不直接修改父组件数据
  2. 基础场景用 props + emit,双向绑定用 defineModel,跨级用 provide/inject
  3. 父调用子必须搭配 defineExpose,这是 Vue3 <script setup> 的强制规则

我可以帮你把这篇博客整理成可直接发布的Markdown文件,还能补充代码高亮、目录和配图说明,需要吗?