如何开发跨框架的组件
2020/7/20 14:04:05
本文主要是介绍如何开发跨框架的组件,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
第 61 篇原创好文~
本文首发于政采云前端团队博客:如何开发跨框架的组件![]()
背景
题主所在的业务中台团队,需要提供业务组件给不同的上层业务方使用,但因为一些历史遗留问题,不同业务线使用的框架不统一,包括 jQuery、React 、Vue 。为了满足不同业务方的需求,往往需要根据业务方使用的框架,开发对应框架的组件。
这样做就会产生一些痛点:
- 每种选型都需要开发一次,费时劳力
- 组件升级,需要业务方同步发版升级,沟通成本高、迭代效率低
理想中的组件
- 跨框架:Write once, run everywhere
- 少升级:组件升级,业务方少升级不升级(注意:组件升级后业务线回归还是必要的)
实现方案
如何设计一个符合上面跨框架、少升级期望的通用方案呢?
很容易想到用原生 JS 来实现,避免跨框架的问题
原生实现
用原生 JS 实现,包含页面里用到的 UI 组件,不依赖任何框架。
优点:
- 跨框架:不依赖于框架实现
- 轻量:可以不依赖其他 UI 组件,体积较小
缺点:
- 投入产出比低:实现一套常用工具方法和 UI 组件,投入时间长
- 踩坑:兼容性问题的坑要走一遍,风险大
- 很难满足复杂业务场景的需求
适用场景:
不需要复杂交互的场景,如前台吊顶、后台菜单侧边栏可采用这种方式。
但在实际的业务场景中,业务组件中有比较多复杂的交互场景, 上面的方案不太能满足要求,所以我们在上面的方案之上进行迭代:
原生容器组件 + iframe 加载业务逻辑组件
我们将业务组件拆分为两部分
一、容器组件:
用原生 JS 实现中间层容器组件,解决跨框架的加载问题,容器组件主要负责:
- 收集组件需要的参数
- 注册全局回调
- 组件挂载
- 加载 iframe
二、业务逻辑组件
根据 iframe 天然的沙箱特性,业务逻辑用 iframe 页面加载,就保证了业务组件的实现不受框架的限制,可以完美解决问题。业务逻辑组件主要负责:
- 与容器组件通信
- 运行环境隔离,可以使用任意框架实现业务逻辑
缺点:
- 动态加载静态资源,iframe 加载略慢,实际体验在接受范围内
- 跨域通信问题
此方案容器组件作为中间层,封装不变的逻辑,将多变的业务逻辑隔离出来,从而保证协作方尽量少升级或不升级。业务定制性可根据接口配置,返回不同的 iframe 地址,加载不同的业务逻辑组件,一次开发任意使用。
如何实现
下面是整个组件的逻辑图
使用方通过容器组件初始化参数、并注册相应的回调:
容器组件
-
初始化:
- 设置 document.domain,让外部组件和 iframe 可以通信
// 获取主域名 function getTopLevelDomain(host) { let data = host || window.location.host; return data.split('.').slice(-2).join('.'); } // 设置主域名 function setDomainToTopLevelDomain() { try { window.document.domain = getTopLevelDomain(); } catch (error) { console.error("设置domain失败") } }
-
render:
- 生成外部容器 div ,设置 loading 图,挂载组件
class Vanilla { // 获取配置信息 constructor(config) { const options = { ...defaultConfig, ...config }; this.options = options; this.elCls = options.elCls; } // 生成容器 div render() { const div = document.createElement('div'); this.el = div; const { width, height } = this.options; div.className = `${prefixCls}-wrap ${prefixCls}-wrap-loading ${this.elCls || ''}`; const maskNode = getMaskNode(prefixCls); const iframeNode = getIframeNode(prefixCls, width, height); div.innerHTML = maskNode + iframeNode; document.body.appendChild(div); this.fn(); } init() { // 设置主域名 setDomainToTopLevelDomain(); // 初始化 div this.render(); // 初始化全局回调函数 this.initCallbacks(); } ... }
-
注册回调函数
- 通过注册全局回调函数,用于业务逻辑组件与容器组件进行通信
class Vanilla { ... initCallbacks() { const self = this; const options = this.options; // 初始化全局变量 window[paramsName] = options; window.onSuccess = function onSuccess(data, res) { options.onSuccess && options.onSuccess(data, res); // 延迟1500ms删除用来显示成功提示 setTimeout(() => { self.removeNode(); }, 1500); self.resetCallbacks && self.resetCallbacks(); }; window.onCancel = function onCancel() { options.onCancel && options.onCancel(); self.removeNode(); self.resetCallbacks && self.resetCallbacks(); }; window.onError = function onError(data) { options.onError && options.onError(data); }; } }
-
加载 iframe 页面:
- 通过接口获取 iframe 地址,业务方可以根据配置动态,加载不同的业务组件
let timer = function timer() {}; class Vanilla { ... $mount() { ... this.fn(); } fn() { const { width, height, isAutoSize, } = this.options; const el = this.el; const url = getContentUrl('你的iframe地址'); const iframeWidth = width; const iframeHeight = height; const iframeEle = el.querySelector('.J_CreditIframe'); const modalNode = el.querySelector(`.${prefixCls}`); if (!isAutoSize && (iframeWidth !== width || iframeHeight !== height)) { this.setNodeSizeAndPostion(modalNode, iframeEle, iframeWidth, iframeHeight); } iframeEle.setAttribute('src', url); // 监听load后,隐藏loading addEvent(iframeEle, 'load', () => { el.className = `${prefixCls}-wrap ${this.elCls || ''}`; const maxTime = 3000; const duration = 1000; let timerCounter = 0; let w = defaultConfig.width; let h = defaultConfig.height; // 自适应宽高 if (isAutoSize) { timer = setInterval(() => { ... // this.setNodeSizeAndPostion(modalNode, iframeEle, scrollWidth, scrollHeight); } timerCounter += duration; if (timerCounter >= maxTime) { clearInterval(timer); } }, duration); } }); } // 设置iframe宽高 setNodeSizeAndPostion(container, iframe, width, height) { container.style.cssText = `width: ${width}px; height: ${height}px;margin-left: -${width / 2}px;margin-top: -${height / 2}px;`; iframe.style.cssText = `width: ${width}px; height: ${height}px;`; } // 删除DOM removeNode() { timer && clearInterval(timer); if (this.el) { document.body.removeChild(this.el); } } ... }
上面我们完成了整个业务组件的加载过程,下面我们需要处理的就是业务逻辑组件如何与容器组件之间进行通信:
通常我们可以这样处理:
// 获取父页面属性 const params = window.parent.paramsName; // 调用父页面方法 window.parent.onSuccess && window.parent.onSuccess(data);
但在实际的业务场景中,我们可能会面临的问题是业务方的域名与 iframe 加载的组件地址域名不一致,这个时候我们就必须要解决组件的跨域通信问题了.
跨域的通信问题
我们可以通过以下三种方式去解决:
postMessage
- postMessage 可以跨文档通信, 在 IE10 的支持性有问题,在 IE11 及以上可以完美解决跨域问题。笔者需要支持IE9+,所以没有采用 postMessage
主域名修改
- document.domain + iframe : 设置 document.domain 为主域名,业务方与 iframe 主域名相同,实现父子同域通信。这种实现的前提是两个域的主域名必须一致。
Nginx 代理
- Nginx 配置:iframe 页面的路径配置为通用路径,反向代理依赖接口,实现全域名可访问。将业务逻辑组件整合到一个或多个项目中使用,组件打包和发布逻辑可单独定制,适合大量跨框架组件。
// 静态页面地址 location ~ ^/your-project/ { root /opt/front/your-project/; try_files $uri $uri/ /index.html = 404; access_log off; } // 反向代理 location ~ ^/api/service/(.*)$ { proxy_pass http://your-ip; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header requestId $request_id; proxy_http_version 1.1; proxy_set_header Connection ""; expires 30d; access_log off; }
需要注意的点
- 注意处理非白色背景的圆角部分,容易出现毛边。处理方法是 iframe 容器不设置背景色,由 iframe 里面设置圆角
- 版本控制:小版本保证向前兼容,大版本可通过动态获取 iframe 地址来实现版本控制
总结
本方案解决了多框架背景下的组件重复开发问题,但根源还是多框架的历史债务问题。更好的方式则是推动技术栈的统一,从根源上避免出现此种情况。
招贤纳士
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com
这篇关于如何开发跨框架的组件的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-30毕设私活神器
- 2024-05-30html
- 2024-05-09一定要避坑:关于微信H5分享,温馨提示你不要再踩坑了!!!
- 2024-05-09本地项目放到公网访问!炒鸡煎蛋!
- 2024-04-07金融企业区域集中库的设计构想和测试验证
- 2024-03-11前端CSS的工程化——掌握Sass这四大特性就够了
- 2024-02-21h5关联css样式(html怎么和css关联)-icode9专业技术文章分享
- 2024-02-07Firefox禁止远程字体加速网页加载及图标字体补充安装
- 2024-02-07一个菜鸡前端的3年总结-「2023」
- 2024-01-18最火前端Web组态软件(可视化)