likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 28

前言

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

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

介绍

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

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

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

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

简单使用

创建 BroadcastChannel Channel

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

const bc = new BroadcastChannel('channel name')

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

接收消息


bc.onmessage = evt => {
  // do something
}

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

发送消息

bc.postMessage("This is a test message.");

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

关闭 Channel

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

bc.close()

兼容性

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

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

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

BroadcastChannel 封装

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

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

// 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 与发布订阅模式结合,有利于跨越不同的功能模块使用

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

on('chatCreated', (evt) => {
  // do something
})

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

emit('chatCreated', true)

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

与 React 集成

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

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

// 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

转载自:https://juejin.cn/post/7360216766373969957
评论
请登录