当前位置: 首页 > news >正文

跨浏览器 Tab 通信工具-emit/on 风格 API(仿 mitt)

文章目录

  • 前言
    • ⚡ 带事件名的 `emit/on` 风格 API(仿 mitt)
      • ✅ 功能:
    • 📁 文件结构
    • 🧩 1. `utils/crossTabEmitter.ts`
    • 🧩 2. `composables/useCrossTabEmitter.ts`
    • ✅ 3. 使用示例
      • 发送事件
      • 接收事件
    • 🎯 用途示例
    • ✅ Bonus:自动管理 + 支持多个频道 + 支持 fallback,几乎所有浏览器都能用。


在这里插入图片描述

前言

封装一个前端跨浏览器 Tab 通信工具


⚡ 带事件名的 emit/on 风格 API(仿 mitt)

✅ 功能:

  • emit(eventName, data) —— 发送带事件名的消息
  • on(eventName, handler) —— 添加监听器
  • off(eventName, handler) —— 移除监听器
  • 自动使用 BroadcastChannel(支持同源标签页)
  • 不支持时自动 fallback 到 localStorage 触发
  • Vue 组件中使用时支持 onBeforeUnmount 自动解绑

📁 文件结构

src/
└── utils/
    └── crossTabEmitter.ts     # 工具核心
└── composables/
    └── useCrossTabEmitter.ts  # Vue composable 包装

🧩 1. utils/crossTabEmitter.ts

type Handler = (data: any) => void;

interface EventMap {
  [eventName: string]: Handler[];
}

export class CrossTabEmitter {
  private channelName: string;
  private bc: BroadcastChannel | null = null;
  private handlers: EventMap = {};
  private fallbackKey: string;

  constructor(channelName: string) {
    this.channelName = channelName;
    this.fallbackKey = `__cross_tab_event__${channelName}`;

    if ('BroadcastChannel' in window) {
      this.bc = new BroadcastChannel(channelName);
      this.bc.onmessage = (e) => this._dispatch(e.data);
    } else {
      window.addEventListener('storage', this._handleStorageEvent);
    }
  }

  private _handleStorageEvent = (e: StorageEvent) => {
    if (e.key === this.fallbackKey && e.newValue) {
      try {
        const { event, data } = JSON.parse(e.newValue);
        this._dispatch({ event, data });
      } catch {}
    }
  };

  private _dispatch({ event, data }: { event: string; data: any }) {
    this.handlers[event]?.forEach((h) => h(data));
  }

  emit(event: string, data: any) {
    const payload = { event, data };

    if (this.bc) {
      this.bc.postMessage(payload);
    } else {
      localStorage.setItem(this.fallbackKey, JSON.stringify(payload));
      localStorage.removeItem(this.fallbackKey);
    }
  }

  on(event: string, handler: Handler) {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event].push(handler);
  }

  off(event: string, handler: Handler) {
    this.handlers[event] = (this.handlers[event] || []).filter((h) => h !== handler);
  }

  destroy() {
    if (this.bc) {
      this.bc.close();
    } else {
      window.removeEventListener('storage', this._handleStorageEvent);
    }
    this.handlers = {};
  }
}

🧩 2. composables/useCrossTabEmitter.ts

import { onBeforeUnmount } from 'vue';
import { CrossTabEmitter } from '@/utils/crossTabEmitter';

const emittersMap: Record<string, CrossTabEmitter> = {};

export function useCrossTabEmitter(channelName = 'global') {
  const emitter = emittersMap[channelName] ||= new CrossTabEmitter(channelName);

  function emit(event: string, data: any) {
    emitter.emit(event, data);
  }

  function on(event: string, handler: (data: any) => void) {
    emitter.on(event, handler);

    onBeforeUnmount(() => {
      emitter.off(event, handler);
    });
  }

  return {
    emit,
    on,
    off: emitter.off.bind(emitter),
  };
}

✅ 3. 使用示例

  • 一个标签页退出登录后,通知其他标签页同时退出登录,清除缓存等。

发送事件

const { emit } = useCrossTabEmitter('auth');
emit('logout', { user: 'admin' });

接收事件

const { on } = useCrossTabEmitter('auth');

on('logout', (payload) => {
  console.log('登出事件收到!', payload);
  // 做登出清理等操作
});

🎯 用途示例

场景事件名数据结构
用户登出通知'logout'{ user: 'admin' }
多标签清除缓存'clear-cache'{ area: 'session' }
登录后刷新页面'login-success'{ token: 'xxx' }

✅ Bonus:自动管理 + 支持多个频道 + 支持 fallback,几乎所有浏览器都能用。

相关文章:

  • 【Unity】Unity Transform缩放控制教程:实现3D模型缩放交互,支持按钮/鼠标/手势操作
  • Python 快速搭建一个小型的小行星轨道预测模型 Demo
  • 裴蜀定理扩展欧几里得定理
  • ssh密钥连接远程服务器并用scp传输文件
  • QAI AppBuilder 快速上手(8): 图像修复应用实例2
  • 网络带宽测速工具选择指南iperf3 nttcp tcpburn jperf使用详解
  • Vue 3 的<Teleport>功能与用法
  • 代码随想录算法训练营第十二天
  • 【ES系列】Elasticsearch从入门到精通保姆级教程 | 启篇
  • Java9新特性
  • Python 并发编程指南:协程 vs 多线程及其他模型比较
  • SpringBoot集成RedisSearch
  • 深度学习|注意力机制
  • 【Java中级】11章、注解、元注解介绍、快速入门,了解java注解的基本使用方式【2】
  • vscode 跳转失败之c_cpp_properties.json解析
  • 【从一个 TypeScript 报错理解 ES6 模块的三种导入方式】
  • 北京自在科技:让万物接入苹果Find My网络的″钥匙匠″
  • sql-labs靶场 less-2
  • PyTorch张量范数计算终极指南:从基础到高阶实战
  • Python: sqlite3.OperationalError: no such table: ***解析