uniapp多个页面监听?全局监听uni.$emit/$on

当需要多个页面/组件同时监听同一个事件 (比如"登录成功""用户信息更新""系统通知")时,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 已执行),都会收到事件通知,各自执行自己的业务逻辑------ 这就是多页面监听的核心:统一事件名 + 各自注册监听

二、全局监听:让监听"永久生效"(不随页面卸载而失效)

上面的示例中,页面卸载后会移除监听(onUnloaduni.$off),如果需要一个「全局生效、只要App/小程序在运行就一直监听」的逻辑(比如监听系统通知、全局错误),需要把 uni.$on 注册在「不会被销毁的全局位置」。

推荐方案:在 App.vue 中注册全局监听

App.vue 是 uni-app 的根组件,整个应用生命周期内只会初始化一次,不会被销毁(除非App/小程序退出),适合注册全局永久监听。

步骤:
  1. App.vueonLaunch(应用启动时)注册监听;
  2. 不需要手动移除监听(因为App退出后内存会自动释放);
  3. 监听函数中可以执行全局逻辑(比如跳转页面、存储全局状态)。
示例:全局监听"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,后期维护方便;
  • 避免重复:工具中判断了重复注册,防止同一个函数被多次执行;
  • 灵活移除:支持移除指定事件、指定函数,或所有事件。

四、关键注意事项

  1. 避免全局监听内存泄漏

    • 页面级监听:必须在 onUnload 中移除(无论是原生 uni.$off 还是封装的 $eventBus.off);
    • 全局监听(App.vue 中):无需移除,因为App退出后内存会释放,但如果是H5端(页面刷新会重新初始化),也可以在 onUnLaunch 中移除(uni-app 3.0+ 支持)。
  2. 事件名统一管理

    • 建议创建 constants/eventNames.js,统一定义事件名(避免拼写错误):

      javascript 复制代码
      // constants/eventNames.js
      export const LOGIN_SUCCESS = "loginSuccess";
      export const SYSTEM_NOTICE = "systemNotice";
    • 使用时导入:

      javascript 复制代码
      import { LOGIN_SUCCESS } from "@/constants/eventNames";
      this.$eventBus.on(LOGIN_SUCCESS, callback);
      this.$eventBus.emit(LOGIN_SUCCESS, userInfo);
  3. 避免滥用全局监听

    • 全局监听会一直占用内存,只用于「真正需要全局响应」的场景(比如系统通知、登录状态变更);
    • 普通跨页面通信(比如A页面给B页面传值),优先用「页面级监听」(页面加载时注册,卸载时移除)。
  4. 数据传递注意引用类型

    • 如果传递对象/数组,多个监听者修改会影响原始数据,如需隔离,可深拷贝:

      javascript 复制代码
      // 发布时深拷贝
      const data = JSON.parse(JSON.stringify(originalData));
      this.$eventBus.emit("eventName", data);

总结

  • 多个页面监听同一事件:直接用 uni.$on 注册相同事件名,uni.$emit 触发即可(自动支持多订阅者);
  • 全局监听:把监听注册在 App.vueonLaunch 中(永久生效),或用封装的 eventBus 工具统一管理;
  • 核心原则:页面级监听"随页生随页死"(记得移除),全局监听"随App生随App死"(按需使用)。

这种方案比 Vuex/Pinia 更轻量,适合简单的全局通知、状态变更场景,搭配之前的 Promise 请求封装,能很好地解决跨页面/组件的通信问题(比如登录失效后通知所有页面刷新数据)。

相关推荐
骨子里的偏爱1 小时前
【案例】uniapp实现内部信息与外部的html网页双向通信的完整的过程,附加完整的代码部分
前端·uni-app·html
郑州光合科技余经理1 小时前
开发指南:海外版外卖跑腿系统源码解析与定制
java·开发语言·mysql·spring cloud·uni-app·php·深度优先
2501_916008891 小时前
提高 iOS 应用逆向难度的工程实践,多工具联动的全栈安全方案
android·安全·ios·小程序·uni-app·cocoa·iphone
爱泡脚的鸡腿1 小时前
uni-app D4 实战(小兔鲜)
前端·vue.js·架构
星火飞码iFlyCode1 小时前
iFlyCode+SpecKit应用:照片等比智能压缩功能实现
前端·javascript
广白2 小时前
钉钉小程序直传文件到 阿里云OSS
前端·vue.js·uni-app
zyfts2 小时前
🔥告别 20 分钟等待!NestJS 生产级消息队列 BullMQ 实践指南
前端·后端
GISer_Jing2 小时前
3DThreeJS渲染核心架构深度解析
javascript·3d·架构·webgl
狗头大军之江苏分军2 小时前
【压力】一位一线炼钢工人的消失
前端·后端