sharedworker 是实现多标签页共用单条 websocket/mqtt 连接的必要机制,连接全生命周期(创建、重连、心跳、路由、销毁)必须由 sharedworker 托管,页面仅通过 postmessage 发送指令并更新 dom。

SharedWorker 是浏览器中唯一能天然支撑多标签页共用单条长连接的机制,它不是“可选优化”,而是实现全局唯一 WebSocket 或 MQTT 连接的必要载体。连接必须完全托管在 SharedWorker 内部——创建、重连、心跳、销毁、消息路由,全部由 worker 主导;页面只负责发指令、收响应,并驱动本地 DOM 更新。
连接逻辑必须 100% 沉入 SharedWorker
WebSocket 实例不可序列化,页面无法通过 postMessage 把已创建的 socket 传给 worker(会直接抛 DataCloneError)。常见错误是试图在 React 组件里 new WebSocket(),再发给 worker —— 这条路走不通。
- worker 脚本(如 socket-worker.js)中直接执行
self.ws = new WebSocket(url),并缓存到全局作用域 - 所有生命周期回调(
onopen、onmessage、onclose、onerror)都写在 worker 内,每个回调加try...catch防止未捕获异常终止 worker - 页面仅发送轻量指令,例如
{ type: 'connect', url: 'wss://api.example.com' },不参与任何底层连接细节
每个页面需携带唯一 clientId 并参与消息路由
多个页面共享一条连接,但服务端下发的消息通常按订阅关系定向推送(如 topic: "order/123" 或 targetClientId: "tab-a7f2")。若 worker 全量广播,会导致状态错乱、UI 误更新。
- 页面首次连接时生成唯一标识:
const clientId = crypto.randomUUID(),并通过port.postMessage({ type: 'init', clientId })注册 - worker 用
Map维护clientId → port映射,同时记录各页面的订阅关系(如哪些 topic) - 收到服务端消息后,解析
targetClientId或匹配topic,只向对应 port 转发,避免跨页面污染 DOM 状态
DOM 视图同步靠精准消息分发 + 主动状态驱动
worker 不接触 DOM,也不调用 setState 或 render。它只确保每条消息准确送达目标页面;真正的视图同步,由各页面自己完成。
- 页面监听
port.onmessage,收到{ type: 'UPDATE', payload: { orderStatus: 'shipped' } }后,触发本地状态更新(如 React 的setOrder(...)) - 对于播放器、计数器等需要强一致性场景,worker 可主动广播全量状态(如
{ type: 'SYNC_STATE', data: { isPlaying: true, time: 124 } }),页面收到后统一 apply 到 DOM 元素 - 页面关闭前应主动发送
{ type: 'disconnect', clientId },worker 清理映射,防止残留端口阻塞消息投递
必须处理端口生命周期与连接保活
SharedWorker 不自动感知页面是否关闭或崩溃。若不管理端口活跃状态,会出现消息发不出、内存泄漏、连接空转等问题。
- 在
self.onconnect中保存 port,并为每个 port 绑定独立onmessage和onclose处理器 - 监听 port 断开(包括页面刷新、崩溃、手动关闭),及时从
Map中移除对应 clientId - 当活跃端口数归零时,主动
ws.close()、清除重连定时器和心跳 interval,避免无效连接占用服务端资源 - 使用指数退避重连(1s → 2s → 4s…上限 30s),心跳由 worker 单点发起(
setInterval(() => ws.send('ping'), 30000)),不依赖页面是否处于前台











