前端analysis | 知其所以然

postMessage vs NgRx

2025-06-02

📌 postMessage vs NgRx —— 技术选型与场景分析


🧭 两种方案的典型使用场景

✅ NgRx 的典型场景

  • 应用运行在 同一个 Angular 应用上下文内
  • 需要集中式状态管理、统一逻辑流(例如全局用户状态、表单状态)
  • 组件之间是父子、兄弟关系,或通过服务可以注入连接

🟢 优点:

  • 类型安全、结构清晰
  • 可调试、可追踪
  • 遵循 Angular 哲学

✅ postMessage 的典型场景

  • 页面包含 跨 iframe、嵌入第三方页面,或与非 Angular 应用交互
  • Angular 无法直接访问或控制通信目标窗口(如嵌入别的系统、老旧页面、跨域)
  • 需要在不同浏览上下文(标签页 / 窗口)之间通信

🟡 优点:

  • 不依赖 Angular,浏览器原生通信能力
  • 适用于任何 HTML 环境
  • 支持跨域

✅ 所以该不该用 postMessage?

👇 以下情况 必须用 postMessage

场景 原因
Angular 页面嵌入到一个父页面(iframe) Angular 没法用 NgRx 和外部通信,只能 postMessage
跨域系统通信(如 SSO 回调、不同子系统协作) 跨域只能用浏览器原生 API
与非 Angular 系统交互(老系统、JS 插件) 无法共享 NgRx store

👇 以下情况 不应该用 postMessage

场景 原因
组件之间通信(兄弟组件、父子组件、跨模块) @Input/@Output、服务共享、NgRx 更合适
所有组件都在 Angular 项目中,能访问公共服务 postMessage 是不必要的复杂化

✅ 建议如何回应同事

可以这样说:

“确实,在 Angular 内部组件通信我们首选 NgRx。但我们这部分通信涉及到 **[嵌套 iframe / 第三方系统 / 跨域应用]**,这些场景 NgRx 是无能为力的,我们只能使用浏览器的 postMessage API 来传递信息。”

或者更技术一点:

“NgRx 是状态管理工具,不是跨窗口通信方案。我们这里是在 DOM 层级结构之间通信,postMessage 是唯一通用、安全、浏览器支持的机制。”


🧩 总结

用途 推荐技术
Angular 内部状态管理 ✅ NgRx
Angular 内部组件通信 @Input/@Output、服务、NgRx
跨 iframe / 跨窗口通信 postMessage
与非 Angular 系统通信 postMessage


❓不同浏览器窗口之间能不能使用 postMessage

简洁回答:可以,但前提是你必须“持有”目标窗口的引用。


🔑 什么是“持有引用”?

在浏览器中,postMessage 的前提是:你必须能访问目标窗口的 window 对象,包括以下几种典型场景。


✅ 可通信的“不同窗口”场景

场景 是否支持 postMessage 如何引用
当前页打开的 window.open() 弹窗 ✅ 支持 window.open() 的返回值就是子窗口引用
弹窗访问其父窗口 (opener) ✅ 支持 window.opener 是父窗口的引用
iframe 与其父窗口之间 ✅ 支持 window.parentiframe.contentWindow
同源标签页之间 ❌ 不支持直接使用 postMessage 可以使用 BroadcastChannel 替代

❌ 不支持的场景(你没引用到目标窗口)

场景 是否支持 说明
页面 A 和 页面 B 是手动打开的两个标签页 ❌ 不支持 没有相互 window 引用,无法通信
浏览器两个独立 tab ❌ 不支持 同上
通过 window.name 找另一个窗口 ❌ 不支持 window.name 只是标识,不是引用

✅ 正确示例:两个窗口互发消息

父窗口:

1
2
3
4
const childWindow = window.open('child.html', '_blank');

// 发送消息到子窗口
childWindow.postMessage({ type: 'HELLO' }, 'https://child-domain.com');

子窗口:

1
2
3
4
5
6
window.addEventListener('message', (event) => {
console.log('收到父窗口消息:', event.data);

// 回复父窗口
event.source.postMessage({ type: 'RECEIVED' }, event.origin);
});

❌ 错误示例:没有引用就不能发

1
2
3
// 页面 A
// 想给用户手动打开的页面 B 发消息
// ❌ 没有任何引用 -> 无法获取 B 的 window -> 不能 postMessage

🧠 如果确实需要“跨标签页通信”怎么办?

✅ 推荐方案:

1
2
3
4
5
6
7
// 使用 BroadcastChannel(同源)
const channel = new BroadcastChannel('my_channel');
channel.postMessage({ action: 'refresh' });

channel.onmessage = (e) => {
console.log('其他 tab 发来消息:', e.data);
};

🔐 安全提醒

无论使用哪种方式:

  • ✅ **验证 event.origin**:确保只接收可信来源的信息
  • ❌ **不要盲目信任 event.data**:加上结构校验、类型判断
  • 避免使用 '*' 作为目标 origin,除非你完全了解并能控制风险

✅ 总结表

场景 是否可以 postMessage
iframe ↔ parent / child ✅ 支持
当前页打开的弹窗(window.open ✅ 支持
弹窗访问其 opener ✅ 支持
手动打开的两个标签页 ❌ 不支持(请用 BroadcastChannel

🚀 替代方案(实现类似跨 tab 通信)

  • BroadcastChannel ✅(同源最推荐)
  • localStorage + storage 事件 ✅(兼容性好)
  • IndexedDB + SharedWorker ✅(适合复杂共享)

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏