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


    前言

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

    如果存在下面情况:

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

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

    图片

    快速开始

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

    npm install bridge-iframe
    # pnpm
    pnpm install bridge-iframe
    # yarn
    yarn add bridge-iframe
    
    • 1
    • 2
    • 3
    • 4
    • 5

    假设页面层级如下:

    • Main
      • Main/Node1

    主页面(Main)

    <h1>Mainh1>
    <iframe src="Node1.html" id="Node1">iframe>
    
    • 1
    • 2
    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    子页面(Node1)

    <h1>Node1h1>
    
    • 1
    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    其中关于请求 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 相对的子页面也创建有名称的桥接对象。

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

    实现原理

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

    网络模型

                             /——> (子节点1)
    (父节点) <———> (节点) <——————> (子节点2)
                             \——> (子节点n) ...
    
    • 1
    • 2
    • 3
    • 每个 节点 都有 上级节点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-1Main

    • <内置协议获取地址>
    • 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 收到响应

    向下请求 MainMain/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-1Main/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-1Main/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 收到响应
  • 相关阅读:
    数据化管理洞悉零售及电子商务运营——数据化管理介绍
    docker镜像创建、删除等相关操作
    【c/c++】c和cpp混合编译
    ubuntu 安装卸载 deb软件
    c#快速获取超大文件夹文件名
    SQLAlchemy列参数的使用和query函数的使用
    51单片机的简易篮球计分器倒计时仿真设计( proteus仿真+程序+原理图+报告+讲解视频)
    [计算机入门] 应用软件(办公类)
    软件测试 - Linux的远程连接
    Netty——网络编程(阻塞理解及代码示例)
  • 原文地址:https://blog.csdn.net/u014771745/article/details/138190046