结论先说

跨线程、跨 iframe、跨标签页传消息,浏览器只给了一个原语:`postMessage`。它没类型、没退订、没请求响应,于是每个项目都要重写一遍同样的胶水。Evil Martians 把这层胶水抽成 `typed-channel`:核心 **271 字节**(压缩后),带 postMessage 传输层 **341 字节**,零依赖;需要「发出去等回话」时再引 `createTypedRpcChannel`,约 1 kB。

裸用 postMessage 的五个坑

以最常见的 Worker 场景为例,页面发任务、Worker 回结果,写法大概是 `switch (event.data.type)` 分流。小脚本没问题,项目一大就出事:

它怎么拆这个问题

作者把职责切成两块:**transport 只负责把消息送过某条边界,channel 只知道有哪些消息、各带什么载荷**。类型写一次,两边的 `emit` 和 `on` 都被 TypeScript 检查。

0.11 补上的那一半:可取消的请求

单向消息之外,真正难写的是「发出去要答案」。新加的 `createTypedRpcChannel` 用 `request` / `handle` 成对出现,并且**请求在类型上被写成一个函数**:参数是发出去的载荷,返回值是拿到的答案(类型映射里写普通值,不写 Promise,处理器可以直接返回或返回 Promise)。

最值得抄的是取消的设计——它**没有**自己做 timeout 选项,而是接受 `AbortSignal`:

取舍要看清

作者明说了两点:`requests()` 运行时零成本,**只做类型推导,不做载荷校验**,结构化克隆照原样传;消息通道和 RPC 通道故意分成两个工厂,不用回话的项目就只付 271 字节。

要试的话,`npm install typed-channel`,先从一个消息映射和 `createTypedChannel` 起步,确实需要答案再加 RPC。真正有价值的地方不是省了那几百字节,而是把「谁跟谁之间有哪些消息」从散落的 switch 收敛成一份可被编译器检查的契约。

阅读原文 → 返回 AI 技术文档

内容与图片版权归原作者所有 · 原文: https://evilmartians.com/chronicles/how-to-make-postmessage-type-safe-in-under-350-bytes