幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

为iframe正名,你可能并不需要微前端

  作者:刘显安(码怪)
  任何新技术、新产品都是有一定适用场景的,它可能在当下很流行,但它不一定在任何时候都是最优解。前言
  最近几年微前端很火,火到有时候项目里面用到了iframe还要偷偷摸摸地藏起来生怕被别人知道了,因为担心被人质疑:你为什么不用微前端方案?直到最近笔者接手一个项目,需要将现有的一个系统整体嵌入到另外一个系统(一共20多个页面),在被微前端坑了几次之后,回过头发现,iframe真香!
  qiankun的作者有一篇《WhyNotIframe》〔1〕介绍了iframe的优缺点(不过作者还有一篇《你可能并不需要微前端》〔2〕给微前端降降火),诚然iframe确实存在很多缺点,但是在选择一个方案的时候还是要具体场景具体分析,它可能在当下很流行,但它不一定在任何时候都是最优解:iframe的这些缺点对我来说是否能够接受?它的缺点是否有其它方法可以弥补?使用它到底是利大于弊还是弊大于利?我们需要在优缺点之间找到一个平衡。优缺点分析
  iframe适合的场景
  由于iframe的一些限制,部分场景并不适合用iframe,比如像下面这种iframe只占据页面中间部分区域,由于父页面已经有一个滚动条了,为了避免出现双滚动条,只能动态计算iframe的内容高度赋值给iframe,使得iframe高度完全撑满,但这样带来的问题是弹窗很难处理,如果居中的话一般弹窗都相对的是iframe内容高度而不是屏幕高度,从而导致弹窗可能看不见,如果固定弹窗top又会导致弹窗跟随页面滚动,而且稍有不慎iframe内容高度计算有一点点偏差就会出现双滚动条。
  所以:如果页面本身比较简单,是一个没有弹窗、浮层、高度也是固定的纯信息展示页的话,用iframe一般没什么问题;如果页面是包含弹窗、信息提示、或者高度不是固定的话,需要看iframe是否占据了全部的内容区域,如果是像下图这种经典的导航菜单内容结构、并且整个内容区域都是iframe,那么可以放心大胆地尝试iframe,否则,需要慎重考虑方案选型。
  为什么一定要满足iframe占据全部内容区域这个条件呢?可以想象一下下面这种场景,滚动条出现在页面中间应该大部分人都无法接受:
  实战:A系统接入B系统
  满足iframe占据全部内容区域条件的场景,iframe的几个缺点都比较好解决。下面通过一个实际案例来详细介绍将一个线上在运行的系统接入到另外一个系统的全过程。以笔者前段时间刚完成的ACP(全称Alibaba。comPay,阿里巴巴国际站旗下一站式全球收款平台,下称A系统)接入生意贷(下称B系统)为例,已知:ACP和生意贷都是MPA页面;ACP系统在此之前没有接入其他系统的先例,生意贷是第一个;生意贷作为被接入系统,本次需要接入的一共有20多个页面,且服务端包含大量业务逻辑以及跳转控制,有些页面想看看长什么样子都非常困难,需要在Node层mock大量接口;接入时需要做功能删减,部分接口入参需要调整;生意贷除了接入到ACP系统中,之前还接入过AMES系统,本次接入需要兼容这部分历史逻辑;
  我们希望的效果:
  假设我们新增一个页面finbase。html?entryxxx作为我们A系统承接B系统的地址,A系统有类似如下代码:classAppextendsReact。Component{state{currentEntry:decodeURIComponent(iutil。getParam(entry)),};render(){returniframeidmicroFrontIframesrca2020imgdataimg。jpgdatasrc{this。state。currentEntry};}}隐藏原系统导航菜单
  因为是接入到另外一个系统,所以需要将原系统的菜单和导航等都通过一个类似hideLayout的参数去隐藏。前进后退处理
  需要特别注意的是,iframe页面内部的跳转虽然不会让浏览器地址栏发生变化,但是却会产生一个看不见的history记录,也就是点击前进或后退按钮(history。forward()或history。back())可以让iframe页面也前进后退,但是地址栏无任何变化。
  所以准确来说前进后退无需我们做任何处理,我们要做的就是让浏览器地址栏同步更新即可。
  如果要禁用浏览器的上述默认行为,一般只能在iframe跳转时通知父页面更新整个DOM节点。URL的同步更新
  让URL同步更新需要处理2个问题,一个是什么时候去触发更新的动作,一个是URL更新的规律,即父页面的URL地址(A系统)与iframe的URL地址(B系统)映射关系的维护。
  保证URL同步更新功能正常需要满足这3种情况:case1:页面刷新,iframe能够加载正确页面;case2:页面跳转,浏览器地址栏能够正确更新;case3:点击浏览器的前进或后退,地址栏和iframe都能够同步变化;什么时候更新URL地址
  首先想到的肯定是在iframe加载完发送一个通知给父页面,父页面通过history。replaceState去更新URL。
  为什么不是history。pushState呢?因为前面提到过,浏览器默认会产生一条历史记录,我们只需要更新地址即可,如果用pushState会产生2条记录。
  B系统:
  A系统:window。addEventListener(message,e{const{data,type}e。data{};if(typeafterHistoryChangedata?。url){这里先采用一个兜底的URL承接任意地址constentryfinbase。html?entry{encodeURIComponent(data。url)};地址不一样才需要更新if(location。pathnamelocation。search!entry){window。history。replaceState(null,,entry);}}});优化URL的更新速度
  按照上面的方法实现后可以发现,URL虽然可以更新但是速度有点慢,点击跳转后一般需要等待7800毫秒地址栏才会更新,有点美中不足。可以把地址栏的更新在跳转后基础之上再加一个跳转前。为此我们必须有一个全局的beforeRedirect钩子,先不考虑它的具体实现:
  B系统:functionbeforeRedirect(href){postMessage(beforeHistoryChange,{url:href});}
  A系统:window。addEventListener(message,e{const{data,type}e。data{};if((typebeforeHistoryChangetypeafterHistoryChange)data?。url){这里先采用一个兜底的URL承接任意地址constentryfinbase。html?entry{encodeURIComponent(data。url)};地址不一样才需要更新if(location。pathnamelocation。search!entry){window。history。replaceState(null,,entry);}}});
  加上上述代码之后,点击iframe中的跳转链接,URL会实时更新,浏览器的前进后退功能也正常。
  为什么需要同时保留跳转前和跳转后呢?因为如果只保留跳转前,只能满足前面的case1和case2,case3无法满足,也就是点击后退按钮只有iframe会后退,URL地址不会更新。美化URL地址
  简单的使用finbase。html?entryxxx这样的通用地址虽然能用,但是不太美观,而且很容易被人看出来是iframe实现的,比较没有诚意,所以如果被接入系统的页面数量在可枚举范围内,建议给每个地址维护一个新的短地址。
  首先,新增一个SPA页面fin。html,和前面的finbase。html指向同一个页面,然后维护一个URL地址的映射,类似这样:A系统地址到B系统地址映射constentryMap{finhome。html:https:fs。alibaba。comxxxhome。htm?hideLayout1,finapply。html:https:fs。alibaba。comxxxapply?hideLayout1,finfailed。html:https:fs。aibaba。comxxxfailed?hideLayout1,省略};constiframeMap{};同时再维护一个子页面父页面URL映射for(constentryinentryMap){iframeMap〔entryMap〔entry〕。split(?)〔0〕〕entry;}classAppextendsReact。Component{state{currentEntry:decodeURIComponent(iutil。getParam(entry))entryMap〔location。pathname〕,};render(){returniframeidmicroFrontIframesrca2020imgdataimg。jpgdatasrc{this。state。currentEntry};}}
  同时完善一下更新URL地址部分:base。html继续用作兜底letentryfinbase。html?entry{encodeURIComponent(data。url)};const〔path,search〕data。url。split(?);if(iframeMap〔path〕){entry{iframeMap〔path〕}?{search};}地址不一样才需要更新if(location。pathnamelocation。search!entry){window。history。replaceState(null,,entry);}
  省略参数透传部分代码。全局跳转拦截
  为什么一定要做全局跳转拦截呢?一个因为我们需要把hideLayout参数一直透传下去,否则就会点着点着突然出现下面这种双菜单的情况:
  另一个是有些页面在被嵌入前是当前页面打开的,但是被嵌入后不能继续在当前iframe打开,比如支付宝付款这种第三方页面,想象一下下面这种情况会不会觉得很怪?所以这类页面一定要做特殊处理让它跳出去而不是当前页面打开。
  URL跳转可以分为服务端跳转和浏览器跳转,浏览器跳转又包括A标签跳转、location。href跳转、window。open跳转、historyAPI跳转等;
  而根据是否新标签打开又可以分为以下4种场景:继续当前iframe打开,需要隐藏原系统的所有layout;当前父页面打开第三方页面,不需要任何layout;新开标签打开第三方页面(如支付宝页面),不需要做特殊处理;新开标签打开宿主页面,需要把原系统layout替换成新layout;
  为此,先定义好一个beforeRedirect方法,由于新标签打开有targetblank和window。open等方式,父页面打开有targetparent和window。parent。location。href等方式,为了更好的统一封装,我们把特殊情况的跳转统一在beforeRedirect处理好,并约定只有有返回值的情况才需要后续继续处理跳转:维护一个需要做特殊处理的第三方页面列表constthirdPageList〔https:service。alibaba。com,https:sale。alibaba。comxxx,https:alipay。comxxx,。。。〕;封装统一的跳转拦截钩子,处理参数透传和一些特殊情况param{}href要跳转的地址,允许传入相对路径param{}isNewTab是否要新标签打开param{}isParentOpen是否要在父页面打开returns返回处理好的跳转地址,如果没有返回值则表示不需要继续处理跳转functionbeforeRedirect(href,isNewTab){if(!href){return;}传过来的href可能是相对路径,为了做统一判断需要转成绝对路径if(href。indexOf(http)!0){varadocument。createElement(a);a。hrefhref;hrefa。href;}如果命中白名单if(thirdPageList。some(itemhref。indexOf(item)0)){if(isNewTab){rawOpen参见后面window。open拦截window。rawOpen(href);}else{第三方页面如果不是新标签打开就一定是父页面打开window。parent。location。hrefhref;}return;}需要从当前URL继续往下透传的参数varparams〔hideLayout,tracelog〕;for(vari0;iparams。length;i){varvaluegetParam(params〔i〕,location。href);if(value){hrefsetParam(params〔i〕,value,href);}}if(isNewTab){letentryfinbase。html?entry{encodeURIComponent(href)};const〔path,search〕href。split(?);if(iframeMap〔path〕){entry{iframeMap〔path〕}?{search};}hrefhttps:payment。alibaba。com{entry};window。rawOpen(href);return;}如果是以iframe方式嵌入,向父页面发送通知postMessage(beforeHistoryChange,{url:href});returnhref;}服务端跳转拦截
  服务端主要是对301或302重定向跳转进行拦截,以Egg为例,只要重写ctx。redirect方法即可。A标签跳转拦截document。addEventListener(click,function(e){vartargete。target{};A标签可能包含子元素,点击目标可能不是A标签本身,这里只简单判断2层if(target。tagNameA(target。parentNodetarget。parentNode。tagNameA)){targettarget。tagNameA?target:target。parentNode;varhreftarget。href;不处理没有配置href或者指向JS代码的A标签if(!hrefhref。indexOf(javascript)0){return;}varnewHrefbeforeRedirect(href,target。targetblank);没有返回值一般是已经处理了跳转,需要禁用当前A标签的跳转if(!newHref){target。targetself;target。hrefjavascript:;;}elseif(newHref!href){target。hrefnewHref;}}},true);location。href拦截
  location。href拦截至今是一个困扰前端界的难题,这里只能采用一个折中的方法:由于location。href无法重写,只能实现一个location2。hrefif(Object。defineProperty){window。location2{};Object。defineProperty(window。location2,href,{get:function(){returnlocation。href;},set:function(href){varnewHrefbeforeRedirect(href);if(newHref){location。hrefnewHref;}},});}
  因为我们不仅实现了location。href的写,location。href的读也一起实现了,所以可以放心大胆的进行全局替换。找到对应前端工程,首先全局搜索window。location。href,批量替换成(window。location2window。location)。href,然后再全局搜索location。href,批量替换成(window。location2window。location)。href(思考一下为什么一定是这个顺序呢)。
  另外需要注意,有些跳转可能是写在npm包里面的,这种情况只能npm也跟着替换一下了,并没有其它更好办法。window。open拦截vartempOpenNamerawOpen;if(!window〔tempOpenName〕){window〔tempOpenName〕window。open;window。openfunction(url,name,features){urlbeforeRedirect(url,true);if(url){window〔tempOpenName〕(url,name,features);}}}history。pushState拦截vartempNamerawPushState;if(!window。history〔tempName〕){window。history〔tempName〕window。history。pushState;window。history。pushStatefunction(state,title,url){urlbeforeRedirect(url);if(url){window。history〔tempName〕(state,title,url);}}}history。replaceState拦截vartempNamerawReplaceState;if(!window。history〔tempName〕){window。history〔tempName〕window。history。replaceState;window。history。replaceStatefunction(state,title,url){urlbeforeRedirect(url);if(url){window。history〔tempName〕(state,title,url);}}}全局loading处理
  完成上述步骤后,基本上已经看不出来是iframe了,但是跳转的时候中间有短暂的白屏会有一点顿挫感,体验不算很流畅,这时候可以给iframe加一个全局的loading,开始跳转前显示,页面加载完再隐藏:
  B系统:document。addEventListener(DOMContentLoaded,function(e){postMessage(iframeDOMContentLoaded,{url:location。href});});
  A系统:window。addEventListener(message,(e){const{data,type}e。data{};iframe加载完毕if(typeiframeDOMContentLoaded){this。setState({loading:false});}if(typebeforeHistoryChange){此时页面并没有立即跳转,需要再稍微等待一下再显示loadingsetTimeout(()this。setState({loading:true}),100);}});
  除此之外还需要利用iframe自带的onload加一个兜底,防止iframe页面没有上报iframeDOMContentLoaded事件导致loading不消失:iframe自带的onload做兜底iframeOnLoad(){this。setState({loading:false});}render(){returnLoadingvisible{this。state。loading}tip正在加载。。。inline{false}iframeidmicroFrontIframesrca2020imgdataimg。jpgdatasrc{this。state。currentEntry}onLoad{this。iframeOnLoad}Loading;}
  还需要注意,当新标签页打开页面时并不需要显示loading,需要注意区分。弹窗居中问题
  当前场景下弹窗个人觉得并不需要处理,因为菜单的宽度有限,不仔细看的话甚至都没注意到弹窗没有居中:
  如果非要处理的话也不麻烦,覆盖一下原来页面弹窗的样式,当包含hideLayout参数时,让弹窗的位置分别向左移动menuWidth2、向上移动navbarHeight2即可(遮罩位置不能动、也动不了)。
  添加了marginLeft120px、marginTop30px后的弹窗效果:
  最终效果
  其实不难看出,最终效果和SPA几乎无异,而且菜单和导航本来就是无刷新的,页面跳转没有割裂感:
  结语
  上述方案有几个没有提到的点:方案成立的前提是建立在2个系统共用一套用户体系,否则需要对2个系统的登录体系进行打通,一般包括账号绑定、A系统默认免登B系统,等等,这需要一定额外的工作量;参数的透传与删除,例如我希望除了hideLayout参数之外其它URL参数全部在父子页面之间透传;埋点,数据上报的时候需要增加一个额外参数来标识流量来自另外一个系统;
  在第一次摸索方案时可能需要花费一些时间,但是在熟悉之后,如果后续还有类似把B系统接入A系统的需求,在没有特殊情况且顺利的前提下可能花费12天时间即可完成,最重要的是大部分工作都是全局生效的,不会随着页面的增多而导致工作量增加,测试回归的成本也非常低,只需要验证所有页面跳转、展示等是否正常,功能本身一般不会有太大问题,而如果是微前端方案的话需要从头到尾全部仔仔细细测试一遍,开发和测试的成本都不可估量。参考资料
  〔1〕
  《WhyNotIframe》:https:www。yuque。comkuitosgky7ywgesexv?spmata。21736010。0。0。25c06df01VID5V
  〔2〕
  《你可能并不需要微前端》:https:zhuanlan。zhihu。comp391248835

诺基亚新发布的这台手机,真的把我看傻了到了2023年,怀旧和情怀依然是很多圈子里,永远不会过气的话题。就像最近火起来的CCD,拍出来的画质并不高清,但大家图的是那份朦胧情怀。手机圈其实也是一样的道理,那……乐享年年增额终身寿险又调整了,还能坚持多久?去年年底,凭借护理险的特殊身份,长期复利超过3。49的乐享年年(又名增多多3号),成为了风险排查风波的幸存者。本来以为这款产品可以独善其身,但是随着最新一期的《人身保险产……外媒2022年,全球海洋温度创1958年有记录以来最高纪录【环球时报综合报道】《大气科学进展》期刊日前发布研究结果称,2022年,全球海洋温度创下1958年有记录以来的最高纪录,自1990年以来全球海洋温度一直呈加速上升趋势。据英国《……景气度进一步回升中国造船业在新年跑出加速度央视网消息:中国船舶工业行业协会的消息,2022年,中国造船产能利用监测指数(CCI)达到764点,是近10年以来的最高点,与2021年相比提高22点,同比增长3。0。上……刺客信条英灵殿获格莱美奖项但被主持人念错名字在今日举行的第65届格莱美上,作曲家StephanieEconomou凭借《刺客信条:英灵殿》DLC末日曙光获得了格莱美电子游戏和其他互动媒体最佳配乐奖,这也是本奖项的首次颁发……爱你依然,想你入心作者:昕月蓝殇等待一个人,是一种无奈,因为不知道,哪一天是归期。走过熟悉的街道,有一些遗憾在心头,因为身旁少了你。往事一幕幕在脑海里浮现,内心又泛起了涟漪。烟火人间……港城的元宵节,有那味儿了元宵节佳节即将到来,连云港市主城区各主干道上两侧挂起大红灯笼,各式灯光雕塑、景观装饰营造出浓浓喜庆氛围。夜色下的连云港在各色灯笼、彩灯的装典下霓虹闪烁,流光溢彩,年味浓浓。……神奇的关元俞,腰背部阳气储藏的穴位,能补肾强骨治疗骨质疏松健康2023今天和大家分享一个可以补肾强骨防治骨质疏松的穴位,关元俞。这是一个位于人体背部的穴位,也是人体腰背部阳气储存的穴位。文章不长,感兴趣的朋友可以认真看完。……数字劳动过程的全球图景,及对中国有什么启示?一、数字劳动过程的国际分工与协作体系数字经济下,无论是在固定网络或移动网络和设备上使用的互联网,都比前几代通信技术提供了更多、更丰富的功能来支持全球经济中更广泛的交易,新……防癌小提示4件事情上,不要过于勤快,避免唤醒癌细胞癌症的发生,与太多因素有关,在已知的危险因素中,大多都来自于生活,可以说癌症的形成,出自于生活的点点滴滴。虽然现在关于癌症预防的宣传教育有很多,但是近年来,我国癌症发病仍……过年回去,终于给老人讲清楚两台电脑是如何通信的本文分享自华为云社区《两台计算机之间究竟是如何通信的?云社区华为云》,作者:龙哥手记。计算机网络的知识点非常杂乱且琐碎,非常容易让人产生畏惧心理。其实计网通篇研究的核心就……苹果营收四年来首次下滑,库克期待中国经济复苏【文观察者网李泽西】当地时间2月2日,苹果公司披露的2023财年第一财季(2022年第四季度)财报显示,总营收为1172。54亿美元,同比下降5。5,低于市场预期的121……
旧衣服怎么处理nbspnbsp它们太多占地方让旧衣服变废为宝,就是变垃圾为资源。从某种意义上说,人类与自然界关系即人类消费方式与自然开发利用关系。生活方式绿色化,绿色发展才有基础。相信很多女生家里都有一堆旧衣服,甚……衣服起球原因有那些nbsp学会这几小技巧让你拥有舒适穿着体验现如今人们越来越注重自己的穿衣打扮,整理衣服的时间却是没有,久而久之就会养成把衣服随手扔进衣柜里的习惯,下面就给大家分享一些保护衣服的小妙招。来源:站酷作者:HighKe……详细文胸的尺码对照表nbsp自己就可估算不被导购忽悠一般女性朋友在购买自己的文胸时,往往选不对适合自己的,有些甚至不知道自己的胸围尺寸是多少,或者有些不知道怎么测量自己的胸围,相信你看完这篇文章就一定会知道了。来源:站酷……文胸尺码表让你了解自己的胸围nbsp选择舒适的文胸很多女性在买文胸时,都没有太多的注意,比如文胸尺码,自己的胸围,就随意看着外观,大小。凭着自己的感觉购买,那是很大的错误,得三思而后行,不然后悔莫及。选好一件合适的文胸让……高秀敏去世前都没与赵本山和解,这段恩怨怪谁?何庆魁给了答案记得小时候,一到过时候看春晚都好期待赵本山和高秀敏的小品。记得从《卖拐》开始,到了下一年就期待大忽悠会是卖什么。一晃,赵本山已经退出了春晚的舞台,高秀敏也去世好多年……瑞士军刀可以托运吗nbsp切不可抱有侥幸心理瑞士军刀是瑞士士兵们专门配用的多功能刀具,它也受到了许多户外活动爱好者的喜爱。但根据我国航空相关规定,刀具是不可以随身携带上飞机的,那瑞士军刀能否被托运呢?有许多男性朋友……白色雪纺衣服被染了怎么办呢nbsp妙招助你白衣亮白如初很多MM都喜欢全白的衣服,白衣飘飘的感觉很干净,很清纯,但也有一个很大的弊端,就是太容易弄脏了,特别是跟其他彩色的衣服一起洗就很容易染上其他的颜色呢。很多MM都喜欢全白的……针织包臀长裙配什么外套好看nbsp达人教你穿出优雅女人味包臀裙,是很多职场达人们很喜欢穿的一款裙装,当然年轻小女生也很喜欢穿包臀裙。那韩式风格的包臀裙,到了冬季应该如何穿最突显女人味呢。为你带来几款超美的包臀裙。包臀裙,是很多……喇叭裤怎么搭配才好看nbsp这几种就是好的可以毫不夸张地说,今年的裤子就是喇叭裤。从去年开始,就已经有很多人穿喇叭裤了,今年更是风靡大街小巷。接下来就解答喇叭裤怎么搭配这个问题。喇叭裤怎么搭配才最显时尚呢,尽管喇……高级定制西装nbsp让你帅气侧漏的秘密对于我们来说,一件好的西装可以提升自己的气质。有条件的人会选择高级定制的西装。高级定制的西装是根据自己的身型来进行裁剪的,更能突显自己的身材。我们都知道,一件西装的好坏主……少女搭配衣服图片展示nbsp甜美风格清新又迷人春夏之初乱穿衣,运动风格的卫衣与少女感的短裙配搭,会让你青春洋溢,透着甜美可爱气息,喜欢韩范儿风格的女生,这样穿试试,很减龄呢。下面一起看看吧。春夏之初乱穿衣,运动风格的……白色雪纺衫配什么外套好看呢nbsp四招清新美搭送给你当冷空气突然袭击,女生们都开始会选择一件合适又潮流的小外套了。不同风格的着装当然会选择不同的小外套了。下面我们来看看白色雪纺衫搭配什么外套好看呢。当冷空气突然袭击,女生们……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网