父子组件通信详解

我们将系统讲解以下几种通信方式:

场景 通信方向 常用机制
父 → 子 父组件传数据给子组件 props
子 → 父 子组件传事件/数据给父组件 emit
父 ↔ 子 父子互相修改数据 v-model(语法糖)
深层通信 任意组件层级间传值 provide / inject
兄弟组件 无父子关系的通信 事件总线 / 状态管理(Pinia / Vuex)

一、父传子 --- props

✅ 基础用法

父组件:

html 复制代码
<template>
  <UserCard :name="userName" :age="userAge" />
</template>

<script setup>
import UserCard from './UserCard.vue'

const userName = 'Alice'
const userAge = 25
</script>

子组件:

html 复制代码
<script setup>
const props = defineProps({
  name: String,
  age: Number
})
</script>

<template>
  <div>{{ name }} --- {{ age }}岁</div>
</template>

🧠 解释:

  • defineProps() 是 Vue 3 <script setup> 提供的编译宏,用于声明接收的属性。

  • 传递数据时建议加 :(动态绑定),否则会被当作字符串。


⚙️ 默认值 & 类型验证

html 复制代码
<script setup>
const props = defineProps({
  name: {
    type: String,
    default: '未命名'
  },
  age: {
    type: Number,
    required: true
  }
})
</script>
  • default:提供默认值

  • required:必传属性

  • type:类型限制(调试模式下会警告)


⚠️ 单向数据流原则

子组件不应直接修改 props!

错误示例:

javascript 复制代码
props.age++ // ❌

正确做法是复制:

javascript 复制代码
const localAge = ref(props.age)

或者通过事件让父组件更新(见下一节)。


二、子传父 --- emit

✅ 基本示例

子组件:

html 复制代码
<script setup>
const emit = defineEmits(['update'])

function handleClick() {
  emit('update', '来自子组件的新数据')
}
</script>

<template>
  <button @click="handleClick">通知父组件</button>
</template>

父组件:

html 复制代码
<template>
  <Child @update="onUpdate" />
</template>

<script setup>
import Child from './Child.vue'

function onUpdate(data) {
  console.log('收到子组件数据:', data)
}
</script>

🧠 解释:

  • 子组件定义 defineEmits(['update'])

  • 使用 emit('update', payload) 触发事件

  • 父组件用 @update 监听


⚙️ 事件参数校验(Vue 3.3+ 新特性)

javascript 复制代码
const emit = defineEmits({
  update(payload) {
    if (typeof payload !== 'string') {
      console.warn('参数必须是字符串')
      return false
    }
    return true
  }
})

三、父子双向通信 --- v-model

Vue 3 中 v-modelprops + emit 的语法糖。

✅ 基础示例

父组件:

html 复制代码
<template>
  <Child v-model="message" />
  <p>父组件数据: {{ message }}</p>
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const message = ref('Hello')
</script>

子组件:

html 复制代码
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

function change() {
  emit('update:modelValue', props.modelValue + ' Vue3')
}
</script>

<template>
  <button @click="change">修改父组件数据</button>
</template>

🧠 底层机制:

父组件写法 子组件内部等价
v-model="data" :modelValue="data" @update:modelValue="data = $event"

⚙️ 多个 v-model

支持多个绑定字段:

父组件:

html 复制代码
<Child v-model:title="title" v-model:content="content" />

子组件:

html 复制代码
<script setup>
const props = defineProps(['title', 'content'])
const emit = defineEmits(['update:title', 'update:content'])
</script>

四、跨层级通信 --- provide / inject

当父组件与子孙组件之间层级较深时,可以使用 依赖注入机制

祖先组件:

html 复制代码
<script setup>
import { provide, ref } from 'vue'

const user = ref('Tom')
provide('user', user)
</script>

<template>
  <ChildA />
</template>

任意后代组件:

html 复制代码
<script setup>
import { inject } from 'vue'
const user = inject('user')
</script>

<template>
  <p>用户: {{ user }}</p>
</template>

🧠 特点:

  • 非响应式变量建议用 ref 包装;

  • 后代组件可以修改注入的值;

  • 常用于全局配置、主题共享、依赖注入(如表单上下文)。


五、兄弟组件通信方案

Vue 没有直接的兄弟通信,需要借助:

1️⃣ 事件总线(Event Bus)

手动创建一个全局事件实例(简单项目可用):

html 复制代码
// eventBus.js
import mitt from 'mitt'
export default mitt()

使用:

html 复制代码
// 组件A
import bus from './eventBus'
bus.emit('change', 123)

// 组件B
import bus from './eventBus'
bus.on('change', val => console.log(val))

2️⃣ 全局状态管理(Pinia / Vuex)

中大型项目推荐使用 Pinia 管理全局状态:

html 复制代码
npm install pinia
html 复制代码
// store.js
import { defineStore } from 'pinia'
export const useMainStore = defineStore('main', {
  state: () => ({ count: 0 }),
})

六、总结对比表

方式 方向 优点 缺点 场景
props 父 → 子 简单直观 单向,不能反向修改 普通父传子
emit 子 → 父 清晰、解耦 只能往上一级传 按钮点击、表单回传
v-model 父 ↔ 子 双向绑定,简洁 内部实现较复杂 表单组件
provide/inject 任意层级 深层传值方便 调试不便 全局配置/依赖注入
事件总线 / Pinia 任意组件 解耦、灵活 管理复杂 大型项目、兄弟通信
相关推荐
Watermelo6173 小时前
从vw/h到clamp(),前端响应式设计的痛点与进化
前端·javascript·css·算法·css3·用户界面·用户体验
寻星探路3 小时前
测试开发话题10---自动化测试常用函数(2)
java·前端·python
Moment3 小时前
快到  2026  年了:为什么我们还在争论  CSS 和 Tailwind?
前端·javascript·css
鸢尾掠地平3 小时前
Python中常用内置函数上【含代码理解】
开发语言·python
梵得儿SHI3 小时前
Vue 核心语法详解:模板语法中的绑定表达式与过滤器(附 Vue3 替代方案)
前端·javascript·vue.js·插值语法·vue模板语法·绑定表达式·过滤器机制
江城开朗的豌豆3 小时前
TypeScript枚举:让你的代码更有"选择权"
前端·javascript
api_180079054603 小时前
请求、认证与响应数据解析:1688 商品 API 接口深度探秘
java·大数据·开发语言·mysql·数据挖掘
唐古乌梁海3 小时前
【Java】JVM 内存区域划分
java·开发语言·jvm
低调小一3 小时前
Android Gradle 的 compileOptions 与 Kotlin jvmTarget 全面理解(含案例)
android·开发语言·kotlin