今天集中学习并复习了 Vue3 中核心的组件通信方式,整理了 Props/Emits、Provide/Inject、Mitt 事件总线三种常用方式的代码实践和核心要点,方便后续复习回顾,也希望帮到正在学习Vue3的你们~
一、 Prop + Emits 基础父子 / 跨级组件通信
这是 Vue 中最基础的组件通信方式,适用于父子组件,也可通过中间组件转发实现跨级(父→子→孙 / 孙→子→父)通信。(关于Props和Emits详情部分可观看前几篇文章)
核心原理
- Props:父组件向子组件传递数据(只读),支持类型校验。
- Emits:子组件向父组件触发自定义事件并传递数据,可声明事件类型。
代码示例(跨级通信)
1. 祖先组件
TypeScript
<script setup lang="ts">
import { ref } from "vue";
import middle from "./study_tree/15.组件通信/17_1.middle.vue"
// 接收子组件传递的参数
function claim(name:string){
alert(name)
}
const name = ref("") // 响应式数据
</script>
<template>
<div>
App
<!-- 父组件向中间组件传值 + 监听事件 -->
<middle name="L" @submit="claim"></middle>
{{ name }}
</div>
</template>
2. 中间组件
TypeScript
<script setup lang="ts">
import Sub from "./17_2.sub1.vue"
// 定义Props类型
interface p {
name:string
}
// 接收父组件传递的Props
const props = defineProps<p>()
// 声明要触发的事件
const emit = defineEmits(['submit'])
// 转发子组件的事件到父组件
function up(name:string){
emit("submit",name)
}
</script>
<template>
<div>
中间组件
<!-- 向孙组件传值 + 监听孙组件事件 -->
<Sub :name="props.name" @submit="up"></Sub>
</div>
</template>
3. 孙组件
TypeScript
<script setup lang="ts">
// 定义Props类型
interface p {
name:string
}
// 接收中间组件传递的Props
const props = defineProps<p>()
// 声明要触发的事件
const emit = defineEmits(['submit'])
// 触发事件,向父组件(中间组件)传值
function up(name:string){
emit("submit",name)
}
</script>
<template>
<div>
孙子组件
name:{{props.name}}
<!-- 点击触发事件,传递参数 -->
<button @click="up('张三')">点击</button>
</div>
</template>
核心要点
- Props 是单向数据流,子组件不能直接修改,需通过 Emits 通知父组件修改。
- 跨级通信时,中间组件需要做 "转发":接收上层的 Props 传递给下层,监听下层的 Emits 再触发给上层。
- 可通过接口(interface)约束 Props 类型,提升代码健壮性。
二、Provide + Inject:跨层级全局共享数据
当组件层级较深时(如父→子→孙→重孙),Props/Emits 转发会很繁琐,Provide/Inject 可实现 "祖先组件提供数据,任意后代组件注入使用",无视层级限制。
核心原理
- Provide :祖先组件通过 **
provide**暴露数据 / 方法,可传递普通值或响应式数据(ref/reactive)。 - Inject :后代组件通过 **
inject**接收祖先提供的数据,直接使用。
代码示例
1. 祖先组件提供数据
TypeScript
<script setup lang="ts">
import { provide, ref } from "vue";
import middle from "./study_tree/15.组件通信/17_1.middle.vue"
// 提供普通数据(非响应式)
provide("msg","L")// 键值 键msg 值"L"
// 提供响应式数据(ref包裹)
const name = ref("")
provide("name",name)
</script>
2. 孙组件注入使用
XML
<script setup lang="ts">
import { inject } from 'vue';
// 注入祖先提供的普通数据
const msg = inject("msg")
// 注入祖先提供的响应式数据
const name = inject("name")
</script>
<template>
<div>
孙子组件
<!-- 使用非响应式数据 -->
通过provide-inject传递: {{msg}}
<br>
<!-- 使用响应式数据(支持v-model双向绑定) -->
通过inject拿到的响应式数据
<input type="text" v-model="name">
{{ name }}
</div>
</template>
核心要点
- 默认情况下 Provide 传递的普通数据是非响应式的,需用
ref/reactive包裹才能保持响应式。 - Inject 可设置默认值:
inject("key", "默认值"),避免数据不存在时报错。 - 适合全局共享的通用数据(如用户信息、主题配置),但过度使用会降低组件独立性。
三、Mitt 事件总线:任意组件通信
Vue3 移除了内置的 EventBus,可通过第三方库 **mitt**实现任意组件(无层级限制)的通信,适用于非父子组件、跨层级组件的通信场景。
核心原理
- 安装 mitt : npm install mitt 。
- 创建全局事件总线实例,通过 emit 触发事件、 on 监听事件、 off 取消监听。
代码示例
1. 创建全局总线
TypeScript
import mitt from "mitt";
const bus = mitt();
export default bus
2. 组件A监听/触发事件
TypeScript
<script setup lang="ts">
import bus from "./until/bus";
// 监听自定义事件 "sun"
bus.on("sun",(data:string)=>{
console.log("App接收到的消息:", data);
})
// 触发自定义事件 "sum"
function sendMsg(){
bus.emit("sum","总结")
}
</script>
<template>
<button @click="sendMsg">通过mitt发送消息到sub</button>
</template>
3. 组件B监听/触发事件
TypeScript
<script setup lang="ts">
import bus from '@/until/bus';
// 触发自定义事件 "sun"
function sendMsg(){
bus.emit("sun","内容")
}
// 监听自定义事件 "sum"
bus.on("sum",(data:string)=>{
console.log("Sub接收到的消息:", data);
})
</script>
<template>
<button @click="sendMsg">通过mitt发送消息到app</button>
</template>
核心要点
- 注意清理事件监听:在组件卸载时( onUnmounted )执行 bus.off("事件名", 回调) ,避免内存泄漏。
- 适合小型项目的非父子组件通信
四、三种通信方式对比
三种方式各有优缺,寻找合适场景使用
表格
| 方式 | 适用场景 | 特点 |
|---|---|---|
| Props +Emits | 父子/跨级通信(层级跨度不高) | 基础、单向数据流 |
| Provide + inject | 跨层级全局共享(层级跨度高) | 无视层级、可响应式 |
| Mitt事件总线 | 任意组件通信(非父子/跨层级) | 灵活、无层级限制 |
五、小结
- 优先使用 Props + Emits 处理父子组件通信,符合 Vue 单向数据流设计理念。
- 跨层级全局共享数据用 Provide + Inject,记得用 ref/reactive 保持响应式。
- 非父子 / 跨层级临时通信可用 Mitt,大型项目建议结合状态管理库(Pinia)。
- 组件通信的核心是 "数据流向清晰",避免滥用全局通信方式导致代码难以维护。
欢迎各位补充说明~