Vue3中的事件總線詳解
在 Vue 3 中,由于創(chuàng)建 Vue 實(shí)例的方式發(fā)生了變化,并且推薦使用 Composition API,事件總線的實(shí)現(xiàn)方式也與 Vue 2 有所不同。
一、Vue 3 事件總線的核心變化
在 Vue 2 中,我們通常 new Vue() 來創(chuàng)建事件總線。但在 Vue 3 中:
- Vue 構(gòu)造函數(shù)不再直接導(dǎo)出,取而代之的是
createApp。 - Vue 實(shí)例不再是一個(gè)事件發(fā)射器,它移除了
$on,$off, 和$once方法。
因此,我們不能再用 new Vue() 來創(chuàng)建事件總線。
二、Vue 3 實(shí)現(xiàn)事件總線的推薦方案
有幾種方式可以在 Vue 3 中實(shí)現(xiàn)事件總線模式,這里介紹最主流和推薦的兩種。
方案一:使用第三方庫mitt(最推薦)
mitt 是一個(gè)小巧(約 200 bytes)、功能完整的的事件總線庫,它不依賴 Vue,但可以完美地與 Vue 3 集成。這是目前 Vue 社區(qū)公認(rèn)的最佳實(shí)踐。
1. 安裝 mitt
npm install mitt
2. 創(chuàng)建事件總線
創(chuàng)建一個(gè)單獨(dú)的文件(如 event-bus.js):
// event-bus.js import mitt from 'mitt'; // 創(chuàng)建一個(gè) mitt 實(shí)例并導(dǎo)出 const emitter = mitt(); export default emitter;
3. 在組件中使用
發(fā)布事件(發(fā)送消息):
使用 emitter.emit(eventType, data)
<!-- PublisherComponent.vue -->
<template>
<button @click="sendMessage">發(fā)送消息</button>
</template>
<script setup>
import { ref } from 'vue';
import emitter from './event-bus.js'; // 導(dǎo)入事件總線
const message = ref('Hello from Vue 3!');
const sendMessage = () => {
// 發(fā)布一個(gè)名為 'messageEvent' 的事件,并攜帶數(shù)據(jù)
emitter.emit('messageEvent', message.value);
// 也可以發(fā)布沒有數(shù)據(jù)的事件
// emitter.emit('someNotification');
};
</script>
訂閱事件(接收消息):
使用 emitter.on(eventType, callback)
關(guān)鍵點(diǎn): 在 Composition API 的 setup 中,需要在 onMounted 生命周期中訂閱,并在 onUnmounted 中取消訂閱,以防止內(nèi)存泄漏。
<!-- SubscriberComponent.vue -->
<template>
<div>收到消息: {{ receivedMessage }}</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import emitter from './event-bus.js'; // 導(dǎo)入事件總線
const receivedMessage = ref('');
// 定義處理事件的函數(shù)
const handleMessage = (data) => {
receivedMessage.value = data;
console.log('收到數(shù)據(jù):', data);
};
// 組件掛載后訂閱事件
onMounted(() => {
emitter.on('messageEvent', handleMessage);
});
// 組件卸載前取消訂閱
onUnmounted(() => {
emitter.off('messageEvent', handleMessage);
});
</script>
mitt 的其他常用 API:
emitter.all: 一個(gè) Map,可以查看所有事件和處理函數(shù)。emitter.off(eventType, handler): 移除特定事件的特定監(jiān)聽器。emitter.emit(eventType): 觸發(fā)事件,可不傳數(shù)據(jù)。
方案二:使用provide/inject實(shí)現(xiàn)一個(gè)簡單的事件總線
如果你不想引入第三方庫,并且應(yīng)用場景比較簡單,可以利用 Vue 3 的 provide 和 inject 特性,在根組件提供一個(gè)響應(yīng)式對(duì)象作為事件總線。
1. 在根組件(如 App.vue)提供事件總線
<!-- App.vue -->
<template>
<PublisherComponent />
<SubscriberComponent />
</template>
<script setup>
import { ref, provide } from 'vue';
import PublisherComponent from './components/PublisherComponent.vue';
import SubscriberComponent from './components/SubscriberComponent.vue';
// 1. 創(chuàng)建一個(gè)響應(yīng)式對(duì)象來存儲(chǔ)事件和監(jiān)聽器(簡化版)
const eventBus = ref({});
// 2. 創(chuàng)建事件總線的方法
const eventBusMethods = {
$on(event, callback) {
if (!eventBus.value[event]) {
eventBus.value[event] = [];
}
eventBus.value[event].push(callback);
},
$emit(event, ...args) {
if (eventBus.value[event]) {
eventBus.value[event].forEach(callback => callback(...args));
}
},
// 可以自行實(shí)現(xiàn) $off 等功能
};
// 3. 將事件總線方法提供給所有子組件
provide('eventBus', eventBusMethods);
</script>
2. 在子組件中注入并使用
發(fā)布者組件:
<!-- PublisherComponent.vue -->
<template>
<button @click="$emitMessage">發(fā)送</button>
</template>
<script setup>
import { inject } from 'vue';
// 注入事件總線
const eventBus = inject('eventBus');
const $emitMessage = () => {
eventBus.$emit('myEvent', '一些數(shù)據(jù)');
};
</script>
訂閱者組件:
<!-- SubscriberComponent.vue -->
<template>
<div>消息: {{ message }}</div>
</template>
<script setup>
import { ref, inject, onMounted, onUnmounted } from 'vue';
// 注入事件總線
const eventBus = inject('eventBus');
const message = ref('');
const handleEvent = (data) => {
message.value = data;
};
onMounted(() => {
eventBus.$on('myEvent', handleEvent);
});
// 注意:這個(gè)簡易實(shí)現(xiàn)需要更復(fù)雜的邏輯來實(shí)現(xiàn) $off,這里省略了取消訂閱
// onUnmounted(() => { ... });
</script>
三、Vue 3 事件總線 vs. 狀態(tài)管理工具(如 Pinia)
| 特性 | 事件總線(如 mitt) | 狀態(tài)管理(如 Pinia) |
|---|---|---|
| 目的 | 組件間通信,傳遞事件和消息 | 全局狀態(tài)管理,集中管理數(shù)據(jù) |
| 數(shù)據(jù)流 | 單向或雙向,通常是“一發(fā)一收” | 單向數(shù)據(jù)流,狀態(tài)可預(yù)測 |
| 數(shù)據(jù)持久性 | 事件觸發(fā)后即消失,不存儲(chǔ)數(shù)據(jù) | 數(shù)據(jù)存儲(chǔ)在 Store 中,是持久的 |
| 適用場景 | 簡單的、一次性的通知(如顯示通知、表單提交完成) | 復(fù)雜的、需要共享和持久化的數(shù)據(jù)(如用戶登錄信息、購物車) |
| 調(diào)試 | 相對(duì)困難,事件是匿名的 | 非常方便,有 DevTools 支持 |
總結(jié)與建議
- 對(duì)于 Vue 3,強(qiáng)烈推薦使用
mitt來實(shí)現(xiàn)事件總線。它輕量、簡單且完美契合 Vue 3 的理念。 - 將事件總線的創(chuàng)建邏輯放在一個(gè)獨(dú)立的模塊中(如
event-bus.js),便于管理和維護(hù)。 - 務(wù)必記住在組件卸載時(shí)(
onUnmounted)取消事件監(jiān)聽,這是避免內(nèi)存泄漏的關(guān)鍵。 - 如果應(yīng)用的數(shù)據(jù)流變得非常復(fù)雜,需要多個(gè)組件共享和修改同一份數(shù)據(jù),請考慮使用 Pinia 這類專門的狀態(tài)管理庫,而不是濫用事件總線。事件總線更適合處理行為和通知,而非管理狀態(tài)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳談Object.defineProperty 及實(shí)現(xiàn)數(shù)據(jù)雙向綁定
這篇文章主要介紹了詳談Object.defineProperty 及實(shí)現(xiàn)數(shù)據(jù)雙向綁定,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
如何利用Vue3+Element?Plus實(shí)現(xiàn)動(dòng)態(tài)標(biāo)簽頁及右鍵菜單
標(biāo)簽頁一般配合菜單實(shí)現(xiàn),當(dāng)你點(diǎn)擊一級(jí)菜單或者二級(jí)菜單時(shí),可以增加對(duì)應(yīng)的標(biāo)簽頁,當(dāng)你點(diǎn)擊對(duì)應(yīng)的標(biāo)簽頁,可以觸發(fā)對(duì)應(yīng)的一級(jí)菜單或者二級(jí)菜單,下面這篇文章主要給大家介紹了關(guān)于如何利用Vue3+Element?Plus實(shí)現(xiàn)動(dòng)態(tài)標(biāo)簽頁及右鍵菜單的相關(guān)資料,需要的朋友可以參考下2022-11-11
Ant Design Vue resetFields表單重置不生效問題及解決
這篇文章主要介紹了Ant Design Vue resetFields 表單重置不生效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
vue3+elementUI如何動(dòng)態(tài)修改Loading中提示文字
這篇文章主要介紹了vue3+elementUI如何動(dòng)態(tài)修改Loading中提示文字的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
淺析Vue3中通過v-model實(shí)現(xiàn)父子組件的雙向數(shù)據(jù)綁定及利用computed簡化父子組件雙向綁定
這篇文章主要介紹了淺析Vue3中通過v-model實(shí)現(xiàn)父子組件的雙向數(shù)據(jù)綁定及利用computed簡化父子組件雙向綁定,需要的朋友可以參考下2022-12-12
解決vue3?defineProps?引入定義的接口報(bào)錯(cuò)
這篇文章主要為大家介紹了解決vue3?defineProps?引入定義的接口報(bào)錯(cuò)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
前端Vue通過Minio返回的URL下載文件實(shí)現(xiàn)方法
Minio是一個(gè)靈活、高性能、開源的對(duì)象存儲(chǔ)解決方案,適用于各種存儲(chǔ)需求,并可以與云計(jì)算、容器化、大數(shù)據(jù)和應(yīng)用程序集成,這篇文章主要給大家介紹了關(guān)于前端Vue通過Minio返回的URL下載文件實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-07-07

