Vue消息訂閱與發(fā)布過程
一、什么是發(fā)布-訂閱模式?
發(fā)布-訂閱模式 是一種對象間通信的設(shè)計模式,包含三個核心角色:
- 發(fā)布者(Publisher):發(fā)出消息(事件)
- 訂閱者(Subscriber):監(jiān)聽并處理消息
- 事件中心(Event Channel):中介,負責消息的注冊與分發(fā)
類比理解
想象一個“廣播電臺”:
- 電臺(事件中心)負責廣播
- 主持人(發(fā)布者)說:“現(xiàn)在是北京時間 8 點整”
- 聽眾(訂閱者)聽到后,各自執(zhí)行動作(起床、煮咖啡、打卡)
發(fā)布者無需知道誰在聽,訂閱者也無需知道誰在說,實現(xiàn)解耦。
二、核心概念:訂閱、發(fā)布、取消訂閱
| 操作 | 方法 | 說明 |
|---|---|---|
| 訂閱 | on(event, callback) | 注冊事件監(jiān)聽器 |
| 發(fā)布 | emit(event, ...args) | 觸發(fā)事件,傳遞數(shù)據(jù) |
| 取消訂閱 | off(event, callback) | 移除監(jiān)聽,防止內(nèi)存泄漏 |
三、在 Vue 中的實現(xiàn)方式
1. Vue 2:使用 Vue 實例作為事件總線
Vue 2 的實例本身實現(xiàn)了事件接口($on、$emit、$off),可直接用作事件中心。
(1) 創(chuàng)建 EventBus
// utils/eventBus.js import Vue from 'vue' // 創(chuàng)建一個 Vue 實例作為全局事件中心 const EventBus = new Vue() export default EventBus
(2) 訂閱消息(在組件中)
<!-- NotificationBar.vue -->
<script>
import EventBus from '@/utils/eventBus'
export default {
data() {
return {
message: ''
}
},
created() {
// 訂閱 'show-notification' 事件
EventBus.$on('show-notification', (msg, type) => {
this.message = `[${type}] ${msg}`
this.show()
})
},
beforeDestroy() {
// 組件銷毀時取消訂閱,避免內(nèi)存泄漏
EventBus.$off('show-notification')
},
methods: {
show() {
// 顯示通知
console.log(this.message)
}
}
}
</script>(3) 發(fā)布消息
<!-- LoginButton.vue -->
<script>
import EventBus from '@/utils/eventBus'
export default {
methods: {
login() {
// 發(fā)布登錄成功事件
EventBus.$emit('user-logged-in', { id: 1, name: 'Alice' })
// 發(fā)布全局通知
EventBus.$emit('show-notification', '登錄成功!', 'success')
}
}
}
</script>實現(xiàn)了跨組件通信,但需手動管理生命周期。
2. Vue 3:使用mitt庫(推薦)
Vue 3 移除了實例上的 $on、$off 方法,因此不能再使用 Vue 實例作為事件總線。
(1) 安裝 mitt
npm install mitt
mitt 是一個超輕量(<200B)的事件發(fā)射器,API 簡潔,完美替代。
(2) 創(chuàng)建全局事件中心
// utils/eventBus.js import mitt from 'mitt' // 創(chuàng)建事件中心 const EventBus = mitt() export default EventBus
(3) 在 Vue 3 組件中使用
<!-- NotificationBar.vue -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import EventBus from '@/utils/eventBus'
// 訂閱事件
const handleNotification = (data) => {
console.log(`[通知] ${data.type}: ${data.msg}`)
}
onMounted(() => {
EventBus.on('show-notification', handleNotification)
})
// 取消訂閱
onUnmounted(() => {
EventBus.off('show-notification', handleNotification)
})
</script><!-- LoginButton.vue -->
<script setup>
import EventBus from '@/utils/eventBus'
function login() {
// 發(fā)布事件
EventBus.emit('user-logged-in', { id: 1, name: 'Bob' })
EventBus.emit('show-notification', {
msg: '登錄成功!',
type: 'success'
})
}
</script>
<template>
<button @click="login">登錄</button>
</template>mitt 支持 TypeScript,API 簡潔:on、emit、off、clear。
四、手寫一個簡易的 Event Hub
為了理解原理,我們來實現(xiàn)一個極簡版的事件中心:
// utils/EventHub.js
class EventHub {
constructor() {
this.events = {}
}
// 訂閱
on(event, callback) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(callback)
}
// 發(fā)布
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(...args))
}
}
// 取消訂閱
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
// 清除所有
clear(event) {
if (event) {
delete this.events[event]
} else {
this.events = {}
}
}
}
export default new EventHub()核心就是維護一個事件-回調(diào)函數(shù)的映射表。
五、發(fā)布-訂閱 vs 其他通信方式
| 通信方式 | 方向 | 耦合度 | 適用場景 |
|---|---|---|---|
| 發(fā)布-訂閱 | 任意 | 低 | 廣播通知、解耦組件 |
| props / emit | 父↔子 | 高 | 緊密相關(guān)的父子組件 |
| Pinia | 任意 | 中 | 共享狀態(tài)、復(fù)雜邏輯 |
| Provide/Inject | 祖→孫 | 中 | 主題、配置傳遞 |
選擇建議:
- 狀態(tài)共享 → Pinia
- 簡單通知 → mitt
- 祖先傳值 → provide/inject
六、最佳實踐與注意事項
1. 使用語義化事件名
// ? 避免模糊命名
EventBus.emit('click')
// ? 使用前綴和語義化命名
EventBus.emit('user:login-success', user)
EventBus.emit('cart:item-added', item)2. 務(wù)必取消訂閱
// Vue 2
beforeDestroy() {
EventBus.$off('event-name')
}
// Vue 3 Composition API
onUnmounted(() => {
EventBus.off('event-name', handler)
})忘記 off 會導(dǎo)致內(nèi)存泄漏和重復(fù)觸發(fā)。
3. 避免過度使用
- 不要用事件總線代替狀態(tài)管理
- 復(fù)雜狀態(tài)同步 → 使用 Pinia
- 事件過多 → 考慮模塊化事件中心
4. TypeScript 支持
// types/events.ts
type Events = {
'user:login': (user: User) => void
'cart:update': (count: number) => void
'app:loading': (status: boolean) => void
}
// utils/eventBus.ts
import mitt from 'mitt'
export const useEventBus = () => mitt<Events>()類型安全,IDE 自動提示。
七、現(xiàn)代替代方案:Pinia + Actions
對于大多數(shù)場景,Pinia 是更優(yōu)解:
// stores/appStore.ts
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({
user: null,
cartCount: 0,
isLoading: false
}),
actions: {
login(user) {
this.user = user
// 其他組件自動響應(yīng)
},
addToCart(item) {
this.cartCount++
},
setLoading(status) {
this.isLoading = status
}
}
})<!-- 任何組件 -->
<script setup>
import { useAppStore } from '@/stores/appStore'
const appStore = useAppStore()
// 直接調(diào)用 action
appStore.login(userData)
</script>響應(yīng)式、可調(diào)試、支持 SSR。
八、總結(jié)
| 核心點 | 說明 |
|---|---|
| 本質(zhì) | 解耦的事件驅(qū)動通信 |
| 角色 | 發(fā)布者、訂閱者、事件中心 |
| Vue 2 | new Vue() 作為 EventBus |
| Vue 3 | 使用 mitt 庫 |
| 優(yōu)點 | 靈活、解耦、輕量 |
| 缺點 | 難以追蹤、易內(nèi)存泄漏 |
| 推薦 | 簡單通知用 mitt,狀態(tài)用 Pinia |
一句話總結(jié):
發(fā)布-訂閱是“消息廣播”,而 Pinia 是“狀態(tài)中心”——根據(jù)場景選擇合適的工具。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
VUE使用 wx-open-launch-app 組件開發(fā)微信打開APP功能
這篇文章主要介紹了VUE使用 wx-open-launch-app 組件開發(fā)微信打開APP功能,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
Vue循環(huán)中多個input綁定指定v-model實例
這篇文章主要介紹了Vue循環(huán)中多個input綁定指定v-model實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08

