
airi × VueUse useBroadcastChannel用响应式通道实现跨窗口通信与舞台状态同步【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airiVueUse 的useBroadcastChannel将浏览器原生的 BroadcastChannel API 封装为一个响应式 Composable以shallowRef承载最新收到的消息用post()向命名信道广播并在组件卸载时自动关闭信道。airi 仓库将其作为舞台Stage子系统的基础设施在桌面版 stage-tamagotchi 的多窗口之间、以及 live2d / mmd / spine / three 多个渲染器包之间同步字幕、语音输入、模型参数等状态。读完本文你可以掌握该 Composable 的完整 API、类型约束与生命周期行为并参考 airi 仓库中真实存在的信道命名、消息定义、跨窗口收发与单元测试写法在自己的 Vue 3 项目中落地类似的跨上下文状态同步方案。BroadcastChannel API 概览根据 VueUse 参考文档useBroadcastChannel是对浏览器 BroadcastChannel API 的响应式封装且会在组件卸载时自动关闭信道。其底层 API 的语义是BroadcastChannel 接口代表一个命名信道同一 origin源下的任意浏览上下文browsing context都可以订阅它它允许同一 origin 的不同文档不同窗口、标签页、frame 或 iframe之间通信消息的广播方式是在信道上的所有BroadcastChannel 对象上触发message事件发送方自己不会收到自己发出的消息useBroadcastChannel在该 API 之上补足了 Vue 生态最缺的三件事响应式数据绑定data自动随message事件更新、SSR 兼容性isSupported守卫以及生命周期托管自动close()。基本用法以下是参考文档中给出的完整用法示例展示了从创建信道到发送、关闭的全流程import { useBroadcastChannel } from vueuse/core import { shallowRef } from vue const { isSupported, channel, post, close, error, isClosed, } useBroadcastChannel({ name: vueuse-demo-channel }) const message shallowRef() message.value Hello, VueUse World! // Post the message to the broadcast channel: post(message.value) // Option to close the channel if you wish: close()几个要点唯一的必填选项是信道名name同名的信道互相收得到对方的消息不同名互不可见——因此信道名本身就是消息协议的第一层路由post(data)即调用底层channel.postMessage(data)消息按结构化克隆structured clone传递因此可以传对象、Map、ArrayBuffer等可克隆值close()是显式关闭手段。即便忘记调用组件卸载时也会自动关闭通常只需关注close()即可覆盖绝大多数场景。选项与类型声明参考文档同时给出了完整的类型声明这里原样继承并逐项解读export interface UseBroadcastChannelOptions extends ConfigurableWindow { /** * The name of the channel. */ name: string } /** * Reactive BroadcastChannel * * see https://vueuse.org/useBroadcastChannel * see https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel * param options * */ export declare function useBroadcastChannelD, P( options: UseBroadcastChannelOptions, ): UseBroadcastChannelReturnD, P export interface UseBroadcastChannelReturnD, P extends Supportable { channel: ShallowRefBroadcastChannel | undefined data: ShallowRefD post: (data: P) void close: () void error: ShallowRefEvent | null isClosed: ShallowRefboolean }各字段的含义返回值类型说明isSupportedComputedRefboolean来自Supportable当前环境是否支持 BroadcastChannel。SSR无window或不支持该 API 的浏览器中为false此时channel保持undefined调用方应据此降级channelShallowRefBroadcastChannel \| undefined底层BroadcastChannel实例的响应式引用。注意类型上允许undefined不支持或未创建时dataShallowRefD最近一条收到的消息。每收到一次message事件就更新是接收端的响应式入口post(data: P) void向信道发送消息close() void显式关闭信道幂等安全配合isClosed判断状态errorShallowRefEvent \| null底层信道error事件捕获到的Event对象isClosedShallowRefboolean信道是否已关闭的响应式标记两个泛型参数DData接收消息类型与PPost发送消息类型解耦了收发两端允许只收不发或收发不同结构的场景。airi 中的用法绝大多数是useBroadcastChannelT, T({ name })即收发同构需要单向接收时如纯监听端则只解构data不传或忽略post亦可因为类型上仍要求Pairi 会显式写成同名类型。选项基类型ConfigurableWindow是 VueUse 的通用可配置项允许注入自定义window/navigator实例例如 Electron 中针对特定webContents的window对象isSupported的判定也基于注入的 window。airi 中的所有调用点均只传了name即默认使用当前全局window。生命周期自动关闭参考文档明确说明 Closes a broadcast channel automatically component unmounted——Composable 将close()注册到当前组件的onScopeDispose组件卸载effect scope 释放时信道被自动关闭。这意味着在组件setup中调用时无需在onUnmounted里手动清理避免泄漏的订阅窗口在 Pinia store 或应用级 Composable 中调用时信道生命周期跟随 store 的 scope通常与整个应用等长这正是 airi 中 store 层大量使用它的原因。airi 中的真实应用以下用例均来自当前仓库源码可逐一在对应文件路径中查看。桌面端跨窗口字幕覆盖层stage-tamagotchi 是一个 Electron 应用源码按main/preload/renderer分层见 electron 主配置其中字幕浮窗是独立于主舞台的窗口。主窗口侧负责发送见 Stage.vueconst { post: postCaption } useBroadcastChannelCaptionChannelEvent, CaptionChannelEvent({ name: airi-caption-overlay }) const { post: postPresent } useBroadcastChannelPresentEvent, PresentEvent({ name: airi-chat-present })独立字幕窗口侧只接收见 caption.vueconst { data } useBroadcastChannelCaptionChannelEvent, CaptionChannelEvent({ name: airi-caption-overlay })两端共享同一个事件类型CaptionChannelEvent与同一个信道名airi-caption-overlay靠post/data的单向流动完成主窗口 → 浮窗的推送。由于 BroadcastChannel 的发送方不会收到自己的消息这里的单向性天然成立不需要额外的去回环逻辑。语音输入信道事件协议 源标识use-hearing-input-channel.ts 展示了更完整的协议设计模式——信道消息携带operation与sourceId两个字段解决多来源轮流写入同一输入框的竞争问题const { data } useBroadcastChannelHearingInputChannelEvent, HearingInputChannelEvent({ name: hearingInputChannelName, }) watch(data, (event) { if (!event) return if (event.operation replace) { if (!event.text.trim()) return if (activeSourceId activeSourceId ! event.sourceId) streamingInput.clear() activeSourceId event.sourceId streamingInput.replace(event.text) return } if (event.sourceId ! activeSourceId) return streamingInput.clear() activeSourceId undefined })要点信道名常量化hearingInputChannelName与事件类型HearingInputChannelEvent都从共享包proj-airi/stage-shared导入收发两端引用同一常量与类型避免字符串漂移datawatch是接收端的标准接线方式useBroadcastChannel把消息事件收敛为 ref 更新业务逻辑用 Vue 原生watch消费保持了与组件内其他响应式逻辑一致的心智模型sourceId防止过期消息污染旧一轮语音utterance的clear事件到达时若新来源已经开始写入直接忽略避免覆盖新内容。对应的单元测试 use-hearing-input-channel.test.ts 值得注意它验证了三个关键行为监听端确实以共享的hearingInputChannelName创建信道expect(...).toHaveBeenCalledWith({ name: hearingInputChannelName })replace操作会替换由本来源拥有的后缀保留用户手动输入的前缀manual note hello world新一轮语音开始后旧轮次的clear被安全忽略stale cleanup。多渲染器包之间的模型参数同步airi 把不同 3D/2D 渲染器拆成独立的包但它们在同一个页面/应用中会共享用户调整模型参数这类状态各自通过useBroadcastChannel挂到独立信道上从源码看属于参数面板 → 各渲染器 store的广播同步模式包信道名源码位置live2dairi-stores-stage-ui-live2dmodel-parameters.tsmmdBroadcastChannelEvents泛型mmd.tsspineBroadcastChannelEvents泛型spine.tsthreeVRMairi-stores-stage-ui-three-vrmmodel-store.ts典型写法一致const { post, data } useBroadcastChannelBroadcastChannelEvents, BroadcastChannelEvents({ name: airi-stores-stage-ui-three-vrm })post用于参数变更后向外广播data用于接收其他端例如参数面板所在上下文的更新。信道名中带有包名前缀airi-stores-stage-ui-*是 airi 中信道命名的一种实际惯例以airi-开头标识归属中段标识模块可搜索、可区分。其他用途流式控制、后台同步与跨标签页同一机制还出现在若干 store 级场景中均遵循命名信道 泛型消息类型的模式流式对话的远端调用turn callsstreaming-control.ts 中const { post: postRemoteCall, data: incomingRemoteCall } useBroadcastChannelRemoteCallMessage, RemoteCallMessage({ name: airi-streaming-control-turn-calls })后台模块的同步信号background.ts 中const { data: syncSignal, post: broadcastSync } useBroadcastChannel({ name: airi:background-sync })——这里展示了不带泛型的调用方式D/P退化为unknown适合纯信号无 payload场景性能追踪桥perf-tracer-bridge.tsSpark 通知桥context-bridge.ts 使用常量SPARK_NOTIFY_BRIDGE_CHANNEL_NAME作为信道名再次印证信道名常量化的项目惯例。isSupported多环境下的守卫模式web 应用侧同样使用该 Composable。例如邮件验证页 verify-email.vueconst { post, data, isSupported } useBroadcastChannelVerifyEmailEvent, VerifyEmailEvent({ ... })这里额外解构了isSupported对于运行在浏览器中的 Web 应用它天然为true但在 SSR 渲染阶段或极少数不支持 BroadcastChannel 的环境中为false此时channel为undefinedUI 逻辑应基于isSupported降级而不是直接访问channel.value。这是文档类型Supportable基接口给出的通用契约airi 的调用点按需选用isSupported或省略同页多标签页同步这种纯浏览器场景下省略也安全。测试模式mock 掉 Composableairi 的测试并不在 Node 环境里真实创建BroadcastChannel而是把vueuse/core整体 mock 成可控对象直接驱动data。参考 use-hearing-input-channel.test.tsconst broadcastChannelMock vi.hoisted(() ({ useBroadcastChannel: vi.fn(), })) vi.mock(vueuse/core, () ({ useBroadcastChannel: broadcastChannelMock.useBroadcastChannel, })) beforeEach(() { data shallowRefHearingInputChannelEvent() broadcastChannelMock.useBroadcastChannel.mockReset() broadcastChannelMock.useBroadcastChannel.mockReturnValue({ data }) })这种模式的好处测试只关注收到某条消息后状态如何变化而把信道是否正确建立压缩成一条toHaveBeenCalledWith({ name: ... })断言。eye-tracking.test.ts 等测试也采用同样的 mock 形状说明这是仓库内处理该 Composable 的统一测试约定。实践建议与适用边界结合参考文档与 airi 的源码模式可以总结出以下可复用的做法信道名是唯一的路由键使用带项目前缀、带模块名的字符串airi 惯例为airi-*/airi:*并集中为常量放在共享包中收发两端引用同一常量消息即协议为每条信道定义独立的 TS 事件类型如CaptionChannelEvent、HearingInputChannelEvent、RemoteCallMessage并在泛型上显式写出useBroadcastChannelD, P让收发结构在编译期可见接收端用datawatch接线发送端只取post单向场景浮窗、监听器只解构data发送方主窗口只解构post职责在代码层面即分离消息携带来源标识当多个生产者竞争同一状态时参考 hearing 信道的sourceId做法在消息中带上轮次/来源 ID避免过期消息覆盖新状态注意 origin 隔离BroadcastChannel 只在同一 origin 的浏览上下文之间通信跨 origin如嵌入的第三方 iframe无法互通从源码结构看airi 桌面端用它同步的是同一应用内的多个窗口/上下文正处在该 API 的设计范围内生命周期无需手工管理组件内使用时卸载即自动close()如需提前释放如切换信道、退出页面阶段再显式调用close()并用isClosed观察状态SSR / 兼容性在服务端渲染或非浏览器环境优先检查isSupported再决定 UI 分支不要假设channel一定可用。参考VueUse Composable 参考文档含用法与类型声明由 vendor 同步维护同步信息见 SYNC.mduseBroadcastChannel.mdComposable 选型总表useBroadcastChannel列于 Browser 分类AUTO调用级别SKILL.mdairi 内真实调用点Stage.vue、use-hearing-input-channel.ts、use-hearing-input-channel.test.ts、model-store.ts、streaming-control.ts、background.ts、verify-email.vue【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考