什么、你竟然没有听过 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

相关推荐
VIP_CQCRE3 小时前
用 Ace Data Cloud 快速接入 Suno 声音克隆 API:让 AI 音乐唱出你的专属声线
ai·aigc·api·音乐生成·suno
先吃饱再说3 小时前
编写一个颜色选择器之后,我理解了 React 工程化的三层架构
react.js·前端框架·前端工程化
先吃饱再说3 小时前
别说你懂 useEffect:从底层机制到生命周期管理,这篇全讲透了
react.js·前端框架
先吃饱再说3 小时前
从“传事件”到“只传值”:React + TypeScript 组件 Props 设计的两次进化
react.js·前端框架·前端工程化
逻极8 小时前
FastAPI 实战:从入门到自动化文档,如何把API开发效率提升200%
python·api·fastapi·swagger·异步
Seven_Ting13 小时前
React-Hooks笔记
前端·笔记·react.js
脉动数据行情21 小时前
脉动行情数据 API 新手对接实战指南
api·股票api·国际期货·国内期货
VIP_CQCRE21 小时前
用 Ace Data Cloud 快速接入 Luma 视频生成 API:让 AI 视频能力进入你的产品
api·ai视频·luma·acedatacloud
meilindehuzi_a1 天前
React Todo 实战:useEffect 生命周期与 localStorage 持久化
前端·javascript·react.js
Revolution611 天前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试