目录
- 1. 事件总线基本概念
- 1.1 什么是事件总线?
- 1.2 创建事件总线
- 2. 事件总线的基本用法
- 2.1 发送事件(发布)
- 2.2 接收事件(订阅)
- 2.3 取消事件监听
- 3. 实际应用场景
- 3.1 用户登录状态通知
- 3.2 购物车更新
- 3.3 全局通知/提示
- 4. 事件总线原理解析
- 4.1 Vue 事件系统的实现
- 5. 最佳实践和注意事项
- 5.1 最佳实践
- 统一管理事件名称
- 使用命名空间
- 自动清理监听器
- 5.2 注意事项
- 内存泄漏风险
- 调试困难
- 6. 替代方案
- Vue 3 中的变化
- 其他通信方式对比
- 7. 总结
- 适用场景:
- 不适用场景:
1. 事件总线基本概念
1.1 什么是事件总线?
事件总线是一个发布-订阅模式的实现,它允许组件之间通过事件进行通信,而不需要直接引用对方。
1.2 创建事件总线
// event-bus.js
import { createApp } from 'vue'
const EventBus = createApp({})
export default EventBus
2. 事件总线的基本用法
2.1 发送事件(发布)
// ComponentA.vue - 发送事件
import EventBus from './event-bus.js'
export default {
methods: {
sendMessage() {
// 发送不带数据的事件
EventBus.$emit('user-login')
// 发送带数据的事件
EventBus.$emit('message-sent', {
text: 'Hello World',
user: 'Alice'
})
// 发送多个参数
EventBus.$emit('data-updated', 'data1', 'data2', 'data3')
}
}
}
2.2 接收事件(订阅)
// ComponentB.vue - 接收事件
import EventBus from './event-bus.js'
export default {
mounted() {
// 监听单个事件
EventBus.$on('user-login', () => {
console.log('用户登录了')
this.handleUserLogin()
})
// 监听带数据的事件
EventBus.$on('message-sent', (message) => {
console.log('收到消息:', message)
this.updateMessage(message)
})
// 监听多个参数的事件
EventBus.$on('data-updated', (arg1, arg2, arg3) => {
console.log('数据更新:', arg1, arg2, arg3)
})
},
methods: {
handleUserLogin() {
// 处理用户登录逻辑
},
updateMessage(message) {
// 更新消息
}
}
}
2.3 取消事件监听
// ComponentB.vue
import EventBus from './event-bus.js'
export default {
mounted() {
// 存储回调函数引用,便于移除
this.messageHandler = (message) => {
console.log('收到消息:', message)
}
EventBus.$on('message-sent', this.messageHandler)
},
beforeDestroy() {
// 移除特定事件的特定回调
EventBus.$off('message-sent', this.messageHandler)
// 移除特定事件的所有回调
EventBus.$off('message-sent')
// 移除所有事件监听器(慎用!)
// EventBus.$off()
}
}
3. 实际应用场景
3.1 用户登录状态通知
// AuthService.js
import EventBus from './event-bus.js'
class AuthService {
login(user) {
// 登录逻辑...
EventBus.$emit('login-success', user)
}
logout() {
// 登出逻辑...
EventBus.$emit('logout')
}
}
// Header.vue - 显示用户信息
EventBus.$on('login-success', (user) => {
this.user = user
this.isLoggedIn = true
})
EventBus.$on('logout', () => {
this.user = null
this.isLoggedIn = false
})
3.2 购物车更新
// ProductItem.vue - 商品组件
methods: {
addToCart(product) {
EventBus.$emit('add-to-cart', product)
}
}
// Cart.vue - 购物车组件
mounted() {
EventBus.$on('add-to-cart', (product) => {
this.cartItems.push(product)
this.updateTotal()
})
}
3.3 全局通知/提示
// NotificationService.js
import EventBus from './event-bus.js'
export const NotificationService = {
success(message) {
EventBus.$emit('notification', { type: 'success', message })
},
error(message) {
EventBus.$emit('notification', { type: 'error', message })
}
}
// Notification.vue - 通知组件
mounted() {
EventBus.$on('notification', (notification) => {
this.showNotification(notification)
})
}
4. 事件总线原理解析
4.1 Vue 事件系统的实现
事件总线的核心是 Vue 实例的事件系统,主要方法:
// Vue 事件系统核心原理简化版
class SimpleEventBus {
constructor() {
this._events = Object.create(null) // 存储所有事件
}
// 监听事件
$on(event, fn) {
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$on(event[i], fn)
}
} else {
(this._events[event] || (this._events[event] = [])).push(fn)
}
return this
}
// 触发事件
$emit(event, ...args) {
const cbs = this._events[event]
if (cbs) {
for (let i = 0; i < cbs.length; i++) {
try {
cbs[i].apply(this, args)
} catch (e) {
console.error(e)
}
}
}
return this
}
// 移除事件监听
$off(event, fn) {
// 如果没有参数,移除所有事件
if (!arguments.length) {
this._events = Object.create(null)
return this
}
// 如果event是数组,递归处理
if (Array.isArray(event)) {
for (let i = 0; i < event.length; i++) {
this.$off(event[i], fn)
}
return this
}
const cbs = this._events[event]
if (!cbs) {
return this
}
// 如果没有指定回调,移除该事件所有监听器
if (!fn) {
this._events[event] = null
return this
}
// 移除指定回调
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return this
}
// 监听一次性事件
$once(event, fn) {
const on = (...args) => {
this.$off(event, on)
fn.apply(this, args)
}
on.fn = fn
this.$on(event, on)
return this
}
}
5. 最佳实践和注意事项
5.1 最佳实践
统一管理事件名称
// event-types.js
export const EVENT_TYPES = {
USER_LOGIN: 'user-login',
USER_LOGOUT: 'user-logout',
CART_UPDATE: 'cart-update',
NOTIFICATION: 'notification'
}
使用命名空间
// 为不同模块使用不同的事件总线
export const AuthEventBus = new Vue()
export const CartEventBus = new Vue()
export const NotificationEventBus = new Vue()
自动清理监听器
// mixins/auto-cleanup.js
export default {
mounted() {
this._eventBusListeners = []
},
methods: {
$onEventBus(event, callback) {
EventBus.$on(event, callback)
this._eventBusListeners.push({ event, callback })
}
},
beforeDestroy() {
if (this._eventBusListeners) {
this._eventBusListeners.forEach(({ event, callback }) => {
EventBus.$off(event, callback)
})
}
}
}
5.2 注意事项
内存泄漏风险
// ❌ 错误:不清理监听器
mounted() {
EventBus.$on('some-event', this.handleEvent)
}
// ✅ 正确:及时清理
beforeDestroy() {
EventBus.$off('some-event', this.handleEvent)
}
调试困难
· 事件流难以追踪
· 建议添加日志记录
// 调试版本的事件总线
const DebugEventBus = {
emit(event, ...args) {
console.log(`[EventBus] Emitting: ${event}`, args)
EventBus.$emit(event, ...args)
},
on(event, callback) {
console.log(`[EventBus] Listening: ${event}`)
EventBus.$on(event, callback)
}
}
6. 替代方案
Vue 3 中的变化
// Vue 3 使用 mitt 或 tiny-emitter
import mitt from 'mitt'
// 创建事件总线
const eventBus = mitt()
// 使用方式类似
eventBus.emit('event')
eventBus.on('event', callback)
其他通信方式对比
· Props/Events: 适合父子组件通信
· Vuex/Pinia: 适合复杂应用状态管理
· Provide/Inject: 适合祖先-后代组件通信
· EventBus: 适合简单跨组件通信
7. 总结
事件总线是一个强大的工具,但需要谨慎使用:
适用场景:
· 简单的跨组件通信
· 全局状态通知
· 第三方插件集成
不适用场景:
· 复杂的状态管理(使用 Vuex/Pinia)
· 数据流需要追踪和调试的场景
· 大型应用的核心状态
正确使用事件总线可以大大简化组件间通信,但过度使用会导致代码难以维护。根据项目规模选择合适的通信方案是关键。