什么、你竟然没有听过 BroadcastChannel?

前言

最近公司的项目有一个需求,当产品在多个标签页上打开时,在其中一个标签页上做了操作,其他页面需要同步对应的状态。

然后我就好好研究了下跨浏览器标签的通信方法,发现了强大的 BroadcastChannel Web API,下面记录我对 BroadcastChannel Web API 学习心得

介绍

Broadcast Channel API 可以实现同源下浏览器不同窗口、Tab 页、frame 或者 iframe 下的浏览器上下文(通常是同一个网站下不同的页面) 之间的简单通信

请看下面这张简单的原理图

通过创建一个监听频道的 BroadcastChannel 对象,然后接收发送给该频道的所有消息,并在它们之间进行双向通信

简单使用

创建 BroadcastChannel Channel

创建 BroadcastChannel Channel 非常简单。通过 new 一个 BroadcastChannel 实例,一个客户端就加入了某个指定的频道。

js 复制代码
const bc = new BroadcastChannel('channel name')

channel name 是必传参数,用来区分不同的 BroadcastChannel

接收消息

js 复制代码
bc.onmessage = evt => {
  // do something
}

当消息被发送之后,所有连接到该频道的 BroadcastChannel 对象上都会触发 message 事件。使用 onmessage 事件处理程序来定义方法来处理消息

发送消息

js 复制代码
bc.postMessage("This is a test message.");

现在发送消息就很简单了,只需要调用 BroadcastChannel 对象上的 postMessage 方法即可。该方法的参数可以是任意的数据类型

关闭 Channel

当不需要使用 BroadcastChannel 时,需要关闭它

js 复制代码
bc.close()

兼容性

通过 Can I Use 可以查看 BoardCastChannel 的各个浏览器支持情况

截止 2024 年,现代化的浏览器基本上都已支持 BoardCastChannel API,所以放心大胆在项目中使用吧

BroadcastChannel 封装

在项目中,每次创建 BoardCastChannel 去发送和接收消息会非常麻烦,下面对 BoardCastChannel 进行简单的封装,方便在项目种使用和后期扩展

将 BoardCastChannel API 与发布订阅模式结合,有利于跨越不同的功能模块使用。完整的实现逻辑如下:

js 复制代码
// createBoardCastChannel.mjs

const createBoardCastChannel = (id) => {
  let listeners = {};

  let bc = new BroadcastChannel(id);

  bc.onmessage = (e) => {
    const { event, data } = e.data;
    const handlers = listeners[event] || [];

    handlers.forEach((h) => h(...data));
  };

  return {
    on(event, callback) {
      if (event in listeners) {
        listeners[event].push(callback);
      } else {
        listeners[event] = [callback];
      }
    },
    emit(event, ...args) {
      bc.postMessage({ event, data: args });
    },
    off(event, callback) {
      if (event in listeners) {
        listeners[event] = listeners[event].filter((h) => h !== callback);
      } else {
        console.log("event not found");
      }
    },
    destroy() {
      bc.close();
    },
    reset() {
      listeners = {};
    },
    count() {
      return Object.keys(listeners).length;
    },
  };
};

export default createBoardCastChannel;

将 BoardCastChannel API 与发布订阅模式结合,有利于跨越不同的功能模块使用

通过传入特定的事件和方法进行订阅

js 复制代码
on('chatCreated', (evt) => {
  // do something
})

然后在需要调用地方使用 emit 发消息

js 复制代码
emit('chatCreated', true)

只要传入不同的事件,就能支持很多状态,非常利于业务拓展

与 React 集成

现在前端的前端项目基本上基于 React 或 Vue 框架开发,所以笔者提供在 React 种如何使用 useBoardCast 的方案

推荐使用 zustand 做全局的状态管理。创建 useBoardCast 方法,然后在任意的组件中使用 useBoardCast

ts 复制代码
// useBoardCast.ts

import { create } from 'zustand'

type EventType = string

type Handler = (...args: unknown[]) => void

interface BoardCastState {
  init: (touchpointId: string) => void
  on(event: EventType, callback: Handler): void
  emit(event: EventType, ...parameters: unknown[]): void
  off(event: EventType, callback: Handler): void
  destroy(): void
  reset(): void
  count(): Record<string, Handler[]>
}

const useBoardCast = create<BoardCastState>()(() => {
  let bc: BroadcastChannel | null = null

  let listeners: Record<string, Handler[]> = {}

  return {
    init(touchpointId: string) {
      bc = new BroadcastChannel(touchpointId)
      bc.onmessage = (e) => {
        const { event, data } = e.data
        const allHandlers = listeners[event]

        allHandlers.forEach((handler) => handler(...data))
      }
    },
    on(event: EventType, callback: Handler) {
      if (event in listeners) {
        listeners[event].push(callback)
      } else {
        listeners[event] = [callback]
      }
    },
    emit(event: EventType, ...parameters) {
      bc?.postMessage({ event, data: parameters })
    },
    off(event: EventType, callback: Handler) {
      const handlers = listeners[event]
      const filterHandlers = handlers.filter((h) => h.toString() !== callback.toString())
      listeners = { ...listeners, [event]: filterHandlers }
    },
    destroy() {
      bc?.close()
    },
    reset() {
      listeners = {}
    },
    count() {
      return listeners
    }
  }
})

export default useBoardCast

场景分析

BroadCastChannel 的原理是基于发布-订阅模式。在这种模式中,有一个或多个发布者(Publishers)负责发布消息,而一个或多个订阅者(Subscribers)则订阅了这些消息。这种模式的优势在于解耦了发布者和订阅者之间的关系,发布者不需要直接知道订阅者的存在,而订阅者也不需要直接知道发布者的身份。这样,系统的可扩展性和灵活性都得到了提高

BroadCastChannel API 的销毁通常是由浏览器自主管理的,而不是由开发者手动控制。当没有任何页面或标签页使用 BroadCastChannel 进行通信时,浏览器可能会自动销毁这些实例,以释放资源并提高性能。然而开发人员也可以通过手动调用 BroadCastChannel 实例的 close() 方法来显式地关闭 channel,以确保在不再需要时及时释放资源。那么这种方式更主动地管理资源,而不依赖于浏览器的自动行为。

总结

Broadcast Channel API 可以实现同源下浏览器不同窗口、Tab 页、frame 或者 iframe 下的浏览器上下文之间的双向通信。BroadCastChannel API 的销毁通常是由浏览器自主管理的,而不是由开发者手动控制。开发人员也可以通过手动调用 close() 方法来显式地关闭 Broadcast Channel

相关推荐
2601_949593655 小时前
基础入门 React Native 鸿蒙跨平台开发:模拟智能音响
react native·react.js·harmonyos
xiaoqi9226 小时前
React Native鸿蒙跨平台如何进行狗狗领养中心,实现基于唯一标识的事件透传方式是移动端列表开发的通用规范
javascript·react native·react.js·ecmascript·harmonyos
jin1233226 小时前
React Native鸿蒙跨平台剧本杀组队消息与快捷入口组件,包含消息列表展示、快捷入口管理、快捷操作触发和消息详情预览四大核心功能
javascript·react native·react.js·ecmascript·harmonyos
烬头88218 小时前
React Native鸿蒙跨平台实现二维码联系人APP(QRCodeContactApp)
javascript·react native·react.js·ecmascript·harmonyos
Li emily9 小时前
如何通过外汇API平台快速实现实时数据接入?
开发语言·python·api·fastapi·美股
xiaoqi92210 小时前
React Native鸿蒙跨平台如何实现分类页面组件通过searchQuery状态变量管理搜索输入,实现了分类的实时过滤功能
javascript·react native·react.js·ecmascript·harmonyos
打小就很皮...10 小时前
Tesseract.js OCR 中文识别
前端·react.js·ocr
qq_1777673710 小时前
React Native鸿蒙跨平台实现应用介绍页,实现了应用信息卡片展示、特色功能网格布局、权限/联系信息陈列、评分展示、模态框详情交互等通用场景
javascript·react native·react.js·ecmascript·交互·harmonyos
jin12332212 小时前
基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
javascript·react native·react.js·ecmascript·harmonyos
2501_9209317012 小时前
React Native鸿蒙跨平台医疗健康类的血压记录,包括收缩压、舒张压、心率、日期、时间、备注和状态
javascript·react native·react.js·ecmascript·harmonyos