如何优雅的实现 iframe 多层级嵌套通讯

前言

在前端开发项目中,不可避免的总会和 iframe 进行打交道,我们通常会使用 postMessage 实现消息通讯。

如果存在下面情况:

  • iframe 父子通讯
  • iframe 同层级通讯
  • iframe 嵌套层级通讯

当面对这种复杂的情况的时候,通讯不可避免成为复杂问题。

快速开始

为了解决这复杂的问题,我开发了 iframe-bridge 来帮助大家优雅的解决这类问题。

bash 复制代码
npm install bridge-iframe
# pnpm
pnpm install bridge-iframe
# yarn
yarn add bridge-iframe

假设页面层级如下:

  • Main
    • Main/Node1

主页面(Main)

html 复制代码
<h1>Main</h1>
<iframe src="Node1.html" id="Node1"></iframe>
javascript 复制代码
import { IFrameBridge, IFrameMessage } from 'bridge-iframe';

// 创建桥接对象
const bridge = new IFrameBridge;
// 连接直接下属节点 Node1 关联 iframe 窗口
birdge.ifrme('Node1', document.getElementById('Node1'));

// 提供给其他 iframe 节点调用的方法(可以定义无数个)
birdge.on('say', async (vo: IFrameMessage) => {
	vo.getData(); // 获取请求数据
	vo.getResult(); // 获取响应数据
	return '来自于 Main';
});

// 等待桥接初始化完成
birdge.ready(async () => {
	console.log('Main 初始化完成!!!');
});

// 等待 Node1 节点桥接完成
birdge.ready('Node1', async () => {
	console.log('Watch Node1 初始化完成!!!');

	// 请求 Node1 的 say 方法
	birdge.request({
		name: 'Node1',
		method: 'say',
	}).then((vo: any) => {
		console.log('在 Main 中请求 Node1.say 方法', vo);
	}).catch((err: any) => {
		console.log('出现错误', err);
	});
});

// 窗口销毁时
bridge.destroy();

子页面(Node1)

html 复制代码
<h1>Node1</h1>
javascript 复制代码
import { IFrameBridge } from 'bridge-iframe';

// 创建桥接对象
const bridge = new IFrameBridge({ name: 'Node1' });

// 提供给其他 iframe 节点调用的方法(可以定义无数个)
birdge.on('say', async (vo: IFrameMessage) => {
	return '来自于 Nodeq';
});

// 等待桥接初始化完成
birdge.ready(async () => {
	console.log('Node1 初始化完成!!!');
});

// 等待 Node1 节点桥接完成
birdge.ready('Main', async () => {
	console.log('Watch Main 初始化完成!!!');

	// 请求 Main 的 say 方法
	birdge.request({
		name: 'Main',
		method: 'say',
	}).then((vo: any) => {
		console.log('在 Node1 中请求 Main.say 方法', vo);
	}).catch((err: any) => {
		console.log('出现错误', err);
	});
});

// 窗口销毁时
bridge.destroy();

其中关于请求 name 在这里称呼为 iframe node 的 域名 作为通讯标识。

关于子节点的名称可以为任意名称,但有两类名称是内置的代表特殊作用不能被使用。

  • Main 作为 主节点/主窗口 的名称地址
  • Parent 作为只请求上一级节点的名称标识,不管上层节点名字是什么

假设页面层级如下(更复杂):

  • Main

    • Main/Node1
      • Main/Node1/Node1-1
      • Main/Node1/Node1-2
    • Main/Node2
      • Main/Node2/Node2-1
      • Main/Node2/Node2-2
  • 在这里还是一样的,创建主页面桥接对象,并关联子页面 iframe 相对的子页面也创建有名称的桥接对象。

  • 还是通过注册一些可以被其他节点调用的方法来实现双通讯的。

实现原理

这里参考了计算机网络的 交换机 的模式来实现跨层级转发。

网络模型

bash 复制代码
                         /------> (子节点1)
(父节点) <---------> (节点) <------------------> (子节点2)
                         \------> (子节点n) ...
  • 每个 节点 都有 上级节点x1 和 下级节点xN 的结构。
  • 消息通讯的核心本质还是 postMessage 来实现。
  • 当消息经过 节点 的时候,通过 message.path 判断 message 是向上 window.parent.postMessage() 传递还是向下 iframe.contentWindow.postMessage() 传递。
  • 当消息经过 节点 的时候,会记录经过的路径为 tracks{ 节点名称, 转发方向 }[] 以此来实现初始地址分配,以及消息返回路径确认。

系统协议

为了实现跨层级通讯,动态为 节点 分配地址,得实现 节点名称映射地址库 来实现。

  • 主窗口/页面提供如下内置方法:
    • @bridge/online 通知主窗口注册地址
    • @bridge/domain 获取名称映射地址
    • @bridge/mapping 获取所有映射地址
  • 所有窗口/页面提供如下内置方法:
    • @bridge/ready 节点准备好了吗?

为了方便调用,定义了如下内置地址:

  • Main 请求主窗口地址
  • Parent 向上级请求窗口(无论层级高低都向上级请求)

通讯模拟:

页面层级

  • Main
    • Main/Node1
      • Main/Node1/Node1-1
      • Main/Node1/Node1-2
    • Main/Node2
      • Main/Node2/Node2-1
      • Main/Node2/Node2-2

向上请求 Main/Node1/Node1-1 到 Main

  • <内置协议获取地址>
  • Main/Node1/Node1-1 请求 ↑↑↑ 到 Main/Node1
    • tracks`{Node1-1:U}`
  • Main/Node1 转发 ↑↑↑ 到 Main
    • tracks`{Node1-1:U}, {Node1:U}`
  • Main 处理逻辑
  • Main 响应 ↓↓↓ 到 Main/Node1
    • tracks`{Node1-1:U}`
  • Main/Node1 转发 ↓↓↓ 到 Main/Node1/Node1-1
    • tracks\[\]
  • Main/Node1/Node1-1 收到响应

向下请求 Main 到 Main/Node1/Node1-1

  • <内置协议获取地址>
  • Main 请求 ↓↓↓ 到 Main/Node1
    • tracks`{Main:D}`
  • Main/Node1 转发 ↓↓↓ 到 Main/Node1/Node1-1
    • tracks`{Main:D}, {Node1:D}`
  • Main/Node1/Node1-1 处理逻辑
  • Main/Node1/Node1-1 响应 ↑↑↑ 到 Main/Node1
    • tracks`{Main:D}`
  • Main/Node1 转发 ↑↑↑ 到 Main
    • tracks\[\]
  • Main 收到响应

同级请求 Main/Node1/Node1-1 到 Main/Node1/Node1-2

  • <内置协议获取地址>
  • Main/Node1/Node1-1 请求 ↑↑↑ 到 Main/Node1
    • tracks`{Node1-1:U}`
  • Main/Node1 转发 ↓↓↓ 到 Main/Node1/Node1-2
    • tracks`{Node1-1:U}, {Node1:D}`]
  • Main/Node1/Node1-2 处理逻辑
  • Main/Node1/Node1-2 响应 ↑↑↑ 到 Main/Node1
    • tracks`{Node1-1:U}`
  • Main/Node1 转发 ↓↓↓ 到 Main/Node1/Node1-1
    • tracks\[\]
  • Main/Node1/Node1-1 收到响应

跨级请求 Main/Node1/Node1-1 到 Main/Node2/Node2-1

  • <内置协议获取地址>
  • Main/Node1/Node1-1 请求 ↑↑↑ 到 Main/Node1
    • tracks`{Node1-1:U}`
  • Main/Node1 转发 ↑↑↑ 到 Main
    • tracks`{Node1-1:U}, {Node1:U}`
  • Main 转发 ↓↓↓ 到 Main/Node2
    • tracks`{Node1-1:U}, {Node1:U}, {Main:D}`
  • Main/Node2 转发 ↓↓↓ 到 Main/Node2/Node2-1
    • tracks`{Node1-1:U}, {Node1:U}, {Main:D}, {Node2:D}`
  • Main/Node2/Node2-1 处理逻辑
  • Main/Node2/Node2-1 响应 ↑↑↑ 到 Main/Node2
    • tracks`{Node1-1:U}, {Node1:U}, {Main:D}`
  • Main/Node2 转发 ↑↑↑ 到 Main
    • tracks`{Node1-1:U}, {Node1:U}`
  • Main 转发 ↓↓↓ 到 Main/Node1
    • tracks`{Node1-1:U}`
  • Main/Node1 转发 ↓↓↓ 到 Main/Node1/Node1-1
    • tracks\[\]
  • Main/Node1/Node1-1 收到响应
相关推荐
liangshiwei1 天前
ABP x Vue:介绍 ABP Vue UI
vue·abp
hasty1 天前
不上传新包,也能改变用户拿到的版本:npm dist-tag 的 OIDC 权限治理
前端·npm·node.js
frjc3 天前
前端技术选型:主流方案对比与落地建议
vue·react·angular
ss2733 天前
AI全栈实战 | 2.3-01 组件库实战:Element Plus 怎么搭出专业界面,过度封装为什么成维护负担
前端·javascript·elementui·vue
玖石书3 天前
HiSH 通过 apk 安装 Node.js、npm 与 pnpm
npm·node.js·鸿蒙·hish
FACELESS VOID3 天前
wsl下使用npm,错误识别到了windows下的npm
linux·windows·npm
搬砖的小码农_Sky4 天前
AI Agent:Windows上Node.js安装后 npm -v报错
前端·npm·node.js·人机交互
zzZ··*5 天前
CodeBuddy 用量看板:本地解析 Token 与积分消耗,不联网不上传
python·vue·ai编程
EatFan5 天前
Go语言全栈实战:基于 Gin + Vue + JWT + RBAC 从零搭建前后端分离权限管理系统
vue.js·golang·go·vue·gin·jwt·rbac
就叫年华吧丶6 天前
长文档点目录定位总是不准?一个 content-visibility + 平滑滚动引发的连环坑(Vue3 实战)
前端·javascript·算法·vue