Vue 3 中的组件通讯是 Vue 应用开发中非常重要的一环,它允许组件之间传递数据和方法,从而实现数据的共享和功能的调用。下面将分别介绍父子组件、孙子组件(祖孙组件)、兄弟组件之间的通讯方式,并给出示例代码和总结表格。
一、父子组件通讯
1. 父传子(props)
父组件通过 props 向子组件传递数据。
父组件示例代码:
<template>
<ChildComponent :message="parentMessage" />
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';
const parentMessage = ref('Hello from Parent');
</script>
子组件示例代码:
<template>
<div>{{ message }}</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
message: String
});
</script>
2. 子传父($emit)
子组件通过 $emit
触发事件,向父组件传递数据或信号。
子组件示例代码:
<template>
<button @click="sendMessage">Send Message to Parent</button>
</template>
<script setup>
//this.$emit->"定义emit",传数据
const emit = defineEmits(['update:message']);
function sendMessage() {
emit('update:message', 'Hello from Child');
}
</script>
父组件示例代码(监听子组件触发的事件):
<template>
<ChildComponent @update:message="handleMessage" />
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
function handleMessage(message) {
console.log(message); // 输出: Hello from Child
}
</script>
二、祖孙组件通讯(provide/inject)
祖先组件 通过 provide
选项提供数据或方法,后代组件 通过 inject
选项接收数据或方法。
祖先组件示例代码:
<template>
<ChildComponent />
</template>
<script setup>
import { provide, ref } from 'vue';
const grandParentData = ref('Data from GrandParent');
provide('grandParentData', grandParentData);
</script>
三、兄弟组件通讯
兄弟组件之间的通讯通常不直接进行,而是通过共同的父组件或全局状态管理(如 Vuex、Pinia)来实现。
示例:通过事件总线(不推荐,Vue 3 中可用第三方库如 mitt 替代)
事件总线(mitt.js):
// eventBus.js
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
组件A(发送方)
<template>
<button @click="sendMessage">Send Message to Brother</button>
</template>
<script setup>
import emitter from './eventBus.js';
function sendMessage() {
emitter.emit('message', 'Hello from Component A');
}
</script>
组件B(接收方):
<template>
<p>{{ message }}</p>
</template>
<script setup>
import { ref } from 'vue';
import emitter from './eventBus.js';
const message = ref('');
//emitter.on接收,'message'通信主体标识
emitter.on('message', (newMessage) => {
message.value = newMessage;
});
</script>
四、总结
通讯类型 | 通讯方式 | 发送方 | 接收方 | 示例说明 |
---|---|---|---|---|
父子组件 | props | 父组件 | 子组件 | 父组件通过 props 传递数据给子组件 |
父子组件 | $emit | 子组件 | 父组件 | 子组件通过 $emit 触发事件,父组件监听并处理 |
祖孙组件 | provide/inject | 祖先组件 | 后代组件 | 祖先组件通过 provide 提供数据,后代组件通过 inject 接收 |
兄弟组件 | 通过父组件 | 兄弟组件A | 兄弟组件B | 兄弟组件A通过事件通知父组件,父组件再传递给兄弟组件B |
兄弟组件 | 全局状态管理(Vuex/Pinia) | 兄弟组件A | 兄弟组件B | 使用 Vuex 或 Pinia 管理全局状态,兄弟组件共享状态 |
兄弟组件 | 第三方库(如 mitt) | 兄弟组件A | 兄弟组件B | 通过第三方库实现事件总线,兄弟组件间通过事件总线通讯 |
五、事件总线底层原理
发布------订阅者模式
发布-订阅模式是一种消息传递模式,允许发送者(发布者)发送消息而不直接关心谁将接收这些消息,同时接收者(订阅者)可以监听并接收这些消息,而不需要了解消息是从哪里发送的。mitt 作为一个轻量级的事件总线库,正是基于这种模式设计的。
在 mitt 中,你可以:
- 使用
.on(eventName, handler)
方法来订阅一个事件,其中eventName
是事件的名称(一个字符串),而handler
是当事件发生时被调用的处理函数。 - 使用
.emit(eventName, ...args)
方法来发布一个事件,其中eventName
是事件的名称,而...args
是传递给事件处理函数的参数列表。 - (可选地)使用
.off(eventName, handler)
方法来取消订阅之前订阅的事件。如果不提供handler
,则取消该事件的所有订阅者。
这些操作完全符合发布-订阅模式的典型特征:
- 发布者 (在 mitt 的上下文中是调用
.emit()
方法的代码)发布一个事件,不需要知道有哪些订阅者会接收这个事件。 - 订阅者 (在 mitt 的上下文中是通过
.on()
方法注册的处理函数)监听并响应特定的事件,不需要知道事件是由谁发布的。 - 事件总线(在 mitt 的上下文中是 mitt 实例本身)负责事件的分发,确保事件能够到达所有相关的订阅者。
因此,可以说 mitt 确实是通过发布-订阅模式来实现其功能的。
// 创建一个简单的发布订阅者管理类
class EventEmitter {
constructor() {
this.events = {}; // 用于存储事件的字典,键为事件名,值为订阅者数组
}
// 订阅事件
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = []; // 如果该事件尚未被订阅,则初始化一个空数组
}
this.events[eventName].push(callback); // 将回调函数添加到订阅者数组中
return this; // 支持链式调用
}
// 取消订阅事件
off(eventName, callback) {
if (this.events[eventName]) {
// 移除指定的回调函数
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
// 如果订阅者数组为空,则删除该事件
if (this.events[eventName].length === 0) {
delete this.events[eventName];
}
}
return this; // 支持链式调用
}
// 发布事件
emit(eventName, ...args) {
if (this.events[eventName]) {
// 遍历订阅者数组,并执行每个回调函数
this.events[eventName].forEach(callback => {
callback.apply(this, args);
});
}
return this; // 支持链式调用
}
// 可选:监听一次后自动取消订阅
once(eventName, callback) {
const onceCallback = (...args) => {
callback.apply(this, args);
this.off(eventName, onceCallback);
};
this.on(eventName, onceCallback);
return this; // 支持链式调用
}
}
// 使用示例
const eventBus = new EventEmitter();
// 订阅者1
function subscriber1(price) {
console.log('订阅者1收到消息:当前价格已降至' + price + '元');
}
// 订阅者2
function subscriber2(price) {
console.log('订阅者2也收到消息:价格更新为' + price + '元');
}
// 订阅事件
eventBus.on('priceUpdate', subscriber1);
eventBus.on('priceUpdate', subscriber2);
// 使用once方法监听一次
eventBus.once('specialOffer', (offer) => {
console.log('只接收一次的特别优惠:' + offer);
});
// 发布事件
eventBus.emit('priceUpdate', 99); // 订阅者1和订阅者2都会收到消息
eventBus.emit('specialOffer', '买一赠一'); // 只有一个订阅者会收到这个特别优惠的消息
// 取消订阅
eventBus.off('priceUpdate', subscriber1);
eventBus.emit('priceUpdate', 88); // 只有订阅者2会收到消息