BroadcastChannel 不能替代 localStorage,需二者配合:用 localStorage 持久化身份数据,BroadcastChannel 广播“请重读”指令,所有窗口以 localStorage 为唯一事实源同步状态。
BroadcastChannel 不能替代 localStorage,但两者配合能解决身份切换时的状态不一致问题——关键在于用 BroadcastChannel 触发同步动作,用 localStorage 持久化身份数据。
localStorage 的 storage 事件只在「其他窗口」调用 setItem() 或 removeItem() 时触发,当前窗口自己改了数据,不会收到自己的事件。这导致一个典型问题:用户在窗口 A 切换身份后,窗口 B 能更新 UI,但窗口 A 自己的头像、菜单、权限状态仍显示旧身份。
常见错误现象包括:storage 监听器没执行、执行了但组件状态没重载、用户登出后当前页还能点进私有路由。
根本原因不是事件没监听上,而是逻辑依赖被动通知,没主动从 localStorage 重新读取。
身份同步不是“发消息让别人变”,而是“告诉所有人:请重新读磁盘”。所有窗口都以 localStorage 为唯一事实源(source of truth),BroadcastChannel 只负责广播“该读了”这个动作。
localStorage.setItem('activeIdentity', JSON.stringify(identity)),再 channel.postMessage({ type: 'IDENTITY_SWITCHED' })
localStorage.getItem('activeIdentity'),不等首次广播IDENTITY_SWITCHED 后,再次调用 localStorage.getItem('activeIdentity'),而不是直接用 event.data.identity
'auth-identity-channel',大小写敏感,且页面加载完成就创建实例快速连续切换身份时,两个 postMessage 可能被浏览器合并或丢失,所以绝不能依赖消息顺序或内容;必须每次以 localStorage 为准。
页面处于后台标签页时,Chrome 会对 onmessage 节流甚至暂停,导致收不到广播。需要加兜底逻辑:当页面 document.visibilityState === 'visible' 时,每 30 秒轮询一次 localStorage 是否变化(仅 visible 时轮询,避免资源浪费)。
组件卸载时必须调用 channel.close(),否则会内存泄漏;但不要在身份切换时关闭频道,也不要跨组件复用未清理的实例。
BroadcastChannel 解决的是「当前所有在线窗口实时同步」,它不跨域、不穿透 sandbox iframe、页面关闭即断开。
如果需要离线状态下缓存切换指令、或结合 Push API 唤醒已关闭的页面,则必须引入 Service Worker,用 self.clients.matchAll() 主动向每个活跃客户端 postMessage()。但这属于增强场景,不是身份同步的必选项。
真正容易被卡住的地方,往往不是 API 不会用,而是没想清楚「谁是权威数据源」——只要把 localStorage 当磁盘、把 BroadcastChannel 当敲钟人,逻辑就稳了。