当需要多个页面/组件同时监听同一个事件 (比如"登录成功""用户信息更新""系统通知")时,uni.$emit/$on 本身就支持「多订阅者」------ 只要多个页面用 相同的事件名 注册 uni.$on,发布事件时所有订阅者都会收到通知。
而"全局监听"本质是让监听逻辑"全局生效"(比如在小程序/App启动时就注册,所有页面都能响应),核心是「选对监听注册的时机和位置」,确保监听不会被意外销毁,且能被所有页面共享。
一、多个页面监听同一事件:基础用法(自动支持)
uni.$on 是全局事件总线,同一个事件名可以注册多个监听函数 ,uni.$emit 触发时会依次执行所有监听。
示例:3个页面同时监听"loginSuccess"事件
1. 页面A(pages/a/a.vue)
vue
<script>
export default {
onLoad() {
// 注册监听:事件名统一为 "loginSuccess"
this.listenerA = (userInfo) => {
console.log("页面A收到登录通知:", userInfo);
// 页面A的业务逻辑:刷新个人中心数据
};
uni.$on("loginSuccess", this.listenerA);
},
onUnload() {
// 页面卸载时移除监听(避免内存泄漏)
uni.$off("loginSuccess", this.listenerA);
}
};
</script>
2. 页面B(pages/b/b.vue)
vue
<script>
export default {
onLoad() {
this.listenerB = (userInfo) => {
console.log("页面B收到登录通知:", userInfo);
// 页面B的业务逻辑:显示登录成功弹窗
uni.showToast({ title: `欢迎回来,${userInfo.nickname}` });
};
uni.$on("loginSuccess", this.listenerB);
},
onUnload() {
uni.$off("loginSuccess", this.listenerB);
}
};
</script>
3. 页面C(pages/c/c.vue)
vue
<script>
export default {
onLoad() {
this.listenerC = (userInfo) => {
console.log("页面C收到登录通知:", userInfo);
// 页面C的业务逻辑:更新购物车权限
};
uni.$on("loginSuccess", this.listenerC);
},
onUnload() {
uni.$off("loginSuccess", this.listenerC);
}
};
</script>
4. 发布事件(登录页)
javascript
// 登录成功后触发事件
uni.$emit("loginSuccess", { nickname: "张三", uid: 123 });
效果:
页面A、B、C只要处于"已加载"状态(onLoad 已执行),都会收到事件通知,各自执行自己的业务逻辑------ 这就是多页面监听的核心:统一事件名 + 各自注册监听。
二、全局监听:让监听"永久生效"(不随页面卸载而失效)
上面的示例中,页面卸载后会移除监听(onUnload 中 uni.$off),如果需要一个「全局生效、只要App/小程序在运行就一直监听」的逻辑(比如监听系统通知、全局错误),需要把 uni.$on 注册在「不会被销毁的全局位置」。
推荐方案:在 App.vue 中注册全局监听
App.vue 是 uni-app 的根组件,整个应用生命周期内只会初始化一次,不会被销毁(除非App/小程序退出),适合注册全局永久监听。
步骤:
- 在
App.vue的onLaunch(应用启动时)注册监听; - 不需要手动移除监听(因为App退出后内存会自动释放);
- 监听函数中可以执行全局逻辑(比如跳转页面、存储全局状态)。
示例:全局监听"systemNotice"系统通知事件
vue
<!-- App.vue -->
<script>
export default {
// 应用启动时执行(只执行一次)
onLaunch() {
console.log("App启动,注册全局监听");
// 注册全局监听:监听 "systemNotice" 事件
uni.$on("systemNotice", (notice) => {
console.log("全局收到系统通知:", notice);
// 全局业务逻辑:
// 1. 存储通知到本地(所有页面都能读取)
uni.setStorageSync("lastNotice", notice);
// 2. 全局弹窗提示(不管当前在哪个页面)
uni.showModal({
title: "系统通知",
content: notice.content,
confirmText: "查看",
success: () => {
// 3. 跳转通知详情页
uni.navigateTo({ url: `/pages/notice/detail?id=${notice.id}` });
}
});
});
}
};
</script>
效果:
无论当前在哪个页面,只要通过 uni.$emit("systemNotice", { id: 1, content: "有新活动" }) 触发事件,全局监听都会执行(弹窗+存储+跳转),所有页面都能感知到该事件。
三、全局监听的进阶方案:封装全局事件管理工具
如果全局事件较多(比如10+个),直接写在 App.vue 中会显得杂乱,建议封装一个「全局事件管理工具」,统一管理订阅、发布、移除逻辑。
步骤1:创建工具文件 utils/eventBus.js
javascript
// 存储全局事件监听(键:事件名,值:监听函数数组)
const eventMap = new Map();
export default {
/**
* 订阅全局事件
* @param {string} eventName 事件名
* @param {Function} callback 监听函数
*/
on(eventName, callback) {
if (!eventMap.has(eventName)) {
eventMap.set(eventName, []);
}
// 避免重复注册同一个函数
const callbacks = eventMap.get(eventName);
if (!callbacks.includes(callback)) {
callbacks.push(callback);
}
},
/**
* 发布全局事件
* @param {string} eventName 事件名
* @param {...any} args 传递的参数
*/
emit(eventName, ...args) {
if (eventMap.has(eventName)) {
// 执行所有监听函数(浅拷贝数组,避免执行中删除导致问题)
const callbacks = [...eventMap.get(eventName)];
callbacks.forEach((cb) => cb(...args));
}
},
/**
* 移除全局事件监听
* @param {string} eventName 事件名(不传则移除所有)
* @param {Function} callback 监听函数(不传则移除该事件所有监听)
*/
off(eventName, callback) {
if (!eventName) {
eventMap.clear(); // 移除所有事件
return;
}
if (eventMap.has(eventName)) {
if (callback) {
// 移除指定函数
const callbacks = eventMap.get(eventName);
eventMap.set(
eventName,
callbacks.filter((cb) => cb !== callback)
);
// 如果该事件没有监听了,删除键
if (eventMap.get(eventName).length === 0) {
eventMap.delete(eventName);
}
} else {
// 移除该事件所有监听
eventMap.delete(eventName);
}
}
}
};
步骤2:在 main.js 中挂载到全局
javascript
import Vue from 'vue';
import App from './App';
import eventBus from './utils/eventBus';
// 挂载到 Vue 原型,所有组件/页面可通过 this.$eventBus 访问
Vue.prototype.$eventBus = eventBus;
Vue.config.productionTip = false;
App.mpType = 'app';
const app = new Vue({
...App
});
app.$mount();
步骤3:全局注册监听(App.vue)
vue
<script>
export default {
onLaunch() {
// 全局监听 "loginSuccess"
this.$eventBus.on("loginSuccess", (userInfo) => {
console.log("全局监听:登录成功", userInfo);
// 全局逻辑:更新全局用户信息
uni.setStorageSync("userInfo", userInfo);
});
// 全局监听 "systemNotice"
this.$eventBus.on("systemNotice", (notice) => {
console.log("全局监听:系统通知", notice);
// 全局弹窗逻辑
});
}
};
</script>
步骤4:页面/组件中使用(多页面监听)
vue
<!-- 页面A -->
<script>
export default {
onLoad() {
this.listener = (userInfo) => {
console.log("页面A监听:登录成功", userInfo);
};
// 订阅事件
this.$eventBus.on("loginSuccess", this.listener);
},
onUnload() {
// 移除监听
this.$eventBus.off("loginSuccess", this.listener);
},
methods: {
triggerEvent() {
// 发布事件
this.$eventBus.emit("loginSuccess", { nickname: "李四" });
}
}
};
</script>
优势:
- 统一管理:所有事件逻辑集中在
eventBus.js,后期维护方便; - 避免重复:工具中判断了重复注册,防止同一个函数被多次执行;
- 灵活移除:支持移除指定事件、指定函数,或所有事件。
四、关键注意事项
-
避免全局监听内存泄漏:
- 页面级监听:必须在
onUnload中移除(无论是原生uni.$off还是封装的$eventBus.off); - 全局监听(
App.vue中):无需移除,因为App退出后内存会释放,但如果是H5端(页面刷新会重新初始化),也可以在onUnLaunch中移除(uni-app 3.0+ 支持)。
- 页面级监听:必须在
-
事件名统一管理:
-
建议创建
constants/eventNames.js,统一定义事件名(避免拼写错误):javascript// constants/eventNames.js export const LOGIN_SUCCESS = "loginSuccess"; export const SYSTEM_NOTICE = "systemNotice"; -
使用时导入:
javascriptimport { LOGIN_SUCCESS } from "@/constants/eventNames"; this.$eventBus.on(LOGIN_SUCCESS, callback); this.$eventBus.emit(LOGIN_SUCCESS, userInfo);
-
-
避免滥用全局监听:
- 全局监听会一直占用内存,只用于「真正需要全局响应」的场景(比如系统通知、登录状态变更);
- 普通跨页面通信(比如A页面给B页面传值),优先用「页面级监听」(页面加载时注册,卸载时移除)。
-
数据传递注意引用类型:
-
如果传递对象/数组,多个监听者修改会影响原始数据,如需隔离,可深拷贝:
javascript// 发布时深拷贝 const data = JSON.parse(JSON.stringify(originalData)); this.$eventBus.emit("eventName", data);
-
总结
- 多个页面监听同一事件:直接用
uni.$on注册相同事件名,uni.$emit触发即可(自动支持多订阅者); - 全局监听:把监听注册在
App.vue的onLaunch中(永久生效),或用封装的eventBus工具统一管理; - 核心原则:页面级监听"随页生随页死"(记得移除),全局监听"随App生随App死"(按需使用)。
这种方案比 Vuex/Pinia 更轻量,适合简单的全局通知、状态变更场景,搭配之前的 Promise 请求封装,能很好地解决跨页面/组件的通信问题(比如登录失效后通知所有页面刷新数据)。