uni-app中应使用纯JS对象实现EventBus,避免Vue实例导致的生命周期问题;需手动绑定/解绑事件,注意函数引用一致性和跨平台时序风险;状态同步优先选用Pinia store。

uni-app 里没有内置 EventBus,得自己造一个
uni-app 官方不提供 EventBus 类或 $bus 实例,Vue 2 的全局事件总线写法(new Vue())在 uni-app 中会出问题:生命周期不一致、页面卸载后监听器残留、H5 和小程序平台行为不统一。直接照搬 Vue 项目里的 EventBus 代码,大概率导致内存泄漏或事件丢失。
正确做法是用一个纯 JavaScript 对象 + 发布订阅模式,避开 Vue 实例的生命周期干扰:
const EventBus = {
events: {},
on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
},
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(...args))
}
},
off(event, callback) {
if (!callback) {
delete this.events[event]
return
}
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback)
}
}
}
export default EventBus
- 所有方法都必须是实例方法(不能用箭头函数定义),否则
this指向会错 - 不要把
EventBus挂到Vue.prototype上——uni-app 的Vue构造函数在不同平台初始化时机不同,挂载后 H5 可能正常,小程序里this.$bus是undefined - 导出后,在页面中用
import EventBus from '@/utils/event-bus.js'显式引入,避免模块缓存或多次实例化
on/emit/off 必须成对使用,尤其注意页面 onUnload 时要 off
uni-app 页面卸载(比如从 A 页面跳转到 B 页面)时,onUnload 钩子会被触发,但组件实例不一定被销毁(尤其是 tabbar 页面),监听器若没手动清除,下次再进该页面又 on 一次,就会重复绑定,导致同一事件触发多次回调。
典型错误写法:onLoad 里只写 EventBus.on('data-updated', this.handleUpdate),却不在 onUnload 或 onHide 里调用 off。
- 推荐在
onLoad中绑定,在onUnload中解绑(非 tabbar 页面);tabbar 页面用onHide/onShow控制更稳妥 - 解绑时传入**同一个函数引用**:
EventBus.off('data-updated', this.handleUpdate),不能传匿名函数或箭头函数 - 如果用 Composition API(
setup),要把回调函数声明为ref或let变量,确保on和off用的是同一个引用
跨平台兼容性:emit 后立即 on 可能收不到事件
在 App 端(尤其是 iOS)和部分小程序平台,JavaScript 执行顺序和事件循环有细微差异,如果 emit 和 on 在同一个 tick 内发生(比如都在 onLoad 里先后调用),监听器可能还没注册完,事件就发出去了,造成“丢了第一发”。
- 规避方式:对关键初始化事件,改用 Promise +
nextTick延迟emit,或让发送方等监听方就绪后再发 - 更稳妥的做法是:把需要“初始化即通信”的逻辑,改成主动拉取(如
EventBus.getData()),而不是依赖广播 - 不要依赖事件触发顺序——
emit不保证同步执行所有监听器,多个on注册顺序和执行顺序也不严格一致
比 EventBus 更轻、更安全的替代方案:Pinia store + $subscribe
如果你只是想在页面间传递状态变更(比如用户登录态、购物车数量),直接用 Pinia 更省心。uni-app 3.0+ 支持 Pinia,且 store.$subscribe 能精准监听字段变化,自动绑定/解绑,不操心内存泄漏。
示例场景:A 页面修改 userStore.token,B 页面立刻响应:
userStore.$subscribe((mutation, state) => {
if (mutation.storeId === 'user' && mutation.type === 'patch object' && 'token' in mutation.payload) {
console.log('token changed:', state.token)
}
})
- 订阅自动随组件卸载清理(只要在
setup或onLoad里调用) - 无需手动
off,无跨平台时序风险 - 但注意:仅适用于状态驱动的场景;如果是临时性、一次性的指令类通信(如“刷新列表”“弹窗确认”),还是得用 EventBus

















