当前位置: 首页 > news >正文

微前端-解决MicroApp微前端内存泄露问题

前言

之前使用京东微前端框架MicroApp集成10个微前端的页面到AngularJs的后台管理系统中,每个微前端做成一个菜单,一共10个,每次打开都是一个新的微前端,但是发现打开的微前端越多,容易造成内存泄露,下面讲解如何解决这个问题。

操作

之前的写法是每个angularjs页面如下所示:

<div class="border-left animated fadeInRight eee-bg-nano" style="border-left: 10px solid #e7eaec;" ng-controller="domainScenarioCtrl"ng-init="init('fireManage')"><div style="width: 100%; height: 100%;"><section style="height: 100%; width: 100%;" class="content" id="fireManage-p"><div style="height: 100%; width: 100%;" id="fireManage"><micro-app style="height: 100%; width: 100%;" name='fireManage'url="http://xxx/iot-front/fireListMagage"></micro-app></div></section></div>
</div>

这样的代码一共有10个文件,这种写法因为是写死的渲染所以容易造成内存泄露,经过询问ChatGpt以及反复验证和尝试,终于得到一个解决办法:

不使用标签,转为使用renderApp()方法动态控制url,每次打开一个tab时只渲染一个微前端,同时卸载其它微前端,也就是不管打开几个微前端页面,有且只有一个是激活状态!

在这里插入图片描述

同时需要解决以下几个场景问题:

1、点击菜单添加tab时激活新的微前端,卸载旧的

2、tab来回切换时激活当前新的微前端,卸载旧的

3、删除tab时激活当前新的微前端,卸载旧的

4、刷新页面时激活当前微前端

5、重复点击菜单tab时激活微前端,卸载旧的

针对以上思路和场景问题,我们一个个来看,首先,我们创建一个文件专门来处理微前端的操作:
micro-app-helper.js

(function (window) {'use strict';const MicroAppHelper = {getAppMap: function () {const context = window.getContext();return {'name1': 'url1',...};},// 处理微应用切换// app=url, alias = ''// global= state = null,sessionStoragehandleMicroAppSwitchByUrl: async function (global, app, isRedirect = false) {if (!app.url) return;try {const appMap = this.getAppMap();let appName = null, appUrl = null;for (const key in appMap) {if (app.url.includes(key)) {appName = key;appUrl = appMap[key];break;}}if (!appName || !appUrl) return;const activeApps = window.microApp.getActiveApps();const activeApp = activeApps.length > 0 ? activeApps[0] : null;const obj = {name: appName, url: appUrl, container: "#" + appName};// 卸载旧应用if (activeApp && activeApp !== appName) {console.log(`卸载旧微前端:${activeApp}`);await window.microApp.unmountApp(activeApp, {clearAliveState: true});const appContainer = document.querySelector('#' + activeApp);if (appContainer) {appContainer.innerHTML = '';console.log(`已移除 DOM 容器:${activeApp}`);}}// 是否需要跳转if (isRedirect) {if (app.alias){// 渲染新应用global.state.go(app.alias).then(() => {this.renderMicroApp(global, obj)});}} else {this.renderMicroApp(global, obj)}} catch (e) {console.error('微应用切换错误:', e);}},renderMicroApp: function (global, obj) {try {const userData = {currentUser: angular.copy(global.sessionStorage.currentUser),userSelOrg: '',selectOrg: angular.copy(global.sessionStorage.checkOrgInfo)};window.microApp.setData(obj.name, userData);window.microApp.renderApp(obj).then(() => {console.log(`${obj.name} 渲染完成`);});} catch (e) {console.log('renderMicroApp error:', e)}},getAppUrlByName: function (name) {const appMap = this.getAppMap();return appMap[name]}};// 暴露到全局window.MicroAppHelper = MicroAppHelper;
})(window);

上面代码中做了几件事:

1、卸载旧的微前端
2、渲染新的微前端
3、向微前端传值setData,注意对应好name,否则不生效
4、还有一些其它业务逻辑,比如angularjs中的$state.go()跳转页面方法,还有根据name匹配到微前端的url,最终在renderApp中做为参数执行,如果大家不需要的话,可以略过,我也懒的改了!

建好之后,我们在项目的index.html中引入这个js文件,否则不生效,如下所示:

<script type="module">// 在主应用中初始化if (!window.microAppInitialized) {import('./js/bundle.js').then((microApp) => {window.microApp = microApp.default || microApp;window.microAppInitialized = true;window.appList = []window.microApp.start({// iframe: true,destroy: true,delay: 0,preFetchApps: [{ name: 'buildingListManage', url: window.getContext().jiBaoUrl + 'iot-front/buildingManage' }, // 加载资源并解析],//预加载lifeCycles: {created(e, appName) {// console.log(`子应用${appName}被创建`)},mounted(e, appName) {console.log(`子应用${appName}已经渲染完成`)},unmount(e, appName) {console.log(`子应用${appName}已经卸载`)},},globalAssets: {js: ['http://xxx/iot-front/static/js/chunk-libs.91a68588.js','http://xxx/iot-front/jquery.min.js','http://xxx/iot-front/static/js/app.c49e13c0.js','http://xxx/iot-front/static/js/chunk-0f0d195a.483813fd.js']}});});}</script><script src="js/lib/microApp/index.js"></script>

bundle.js就是micro-app的源码,因为是angularjs项目,所以我直接这样写了,如果是vue和react的话,应该直接import就好!这里做了预加载以及初始化,同时把刚才的js文件引入!

1、点击菜单添加tab时激活新的微前端,卸载旧的
注意1和4虽然场景不同,但是效果一样,我们怎么处理呢,代码如下所示:

<div class="border-left animated fadeInRight eee-bg-nano" style="border-left: 10px solid #e7eaec;" ng-controller="domainScenarioCtrl"ng-init="init('fireManage')"><div style="width: 100%; height: 100%;"><div style="height: 100%; width: 100%;" id="fireManage"></div></div>
</div>

我们以这个页面做示例,我们调用init方法来渲染微前端页面,sceneName就是id名,要一致,否则不生效!

 $scope.init = function(sceneName) {MicroAppHelper.handleMicroAppSwitchByUrl({sessionStorage: $sessionStorage,state: $state,},{url: sceneName,alias: ''},false);})

这样不管是点击打开新的微前端还是刷新页面都会调用这个init方法,就能实时渲染微前端页面了!

2、tab来回切换时激活当前新的微前端,卸载旧的

注意:2、3、5我放在一起讲了,因为代码在一起。

因为我使用的是layui的tab组件,所以下面代码只有参考价值,毕竟大家应该都是用的新的UI技术了。

 $scope.initContent = function () {if (IBE.CONFIG.multiTab) {$scope.showMultiTab = true;let list = []//监听tab变化,不管是增加、删除还是点击,一律添加hash值layui.element.on('tab(contentab)', function (obj) {const thisUrl = $(this).attr("data-url");const thisAlias = $(this).attr('id');const alias = thisAlias.split('_')// 这个hash一定要加,否则打开新的tab不会被选中!location.hash = thisUrl;// 如果是已经打开过的,则直接激活if ($rootScope.isMenuTrigger){MicroAppHelper.handleMicroAppSwitchByUrl({sessionStorage: $sessionStorage,state: $state,},{url: thisUrl,alias: alias.join('.')},false);}})// 监听删除tab事件layui.element.on('tabDelete(contentab)', function (obj) {try{//获取删除后激活的tab元素const $tabs = $(obj.elem).find('li');  // 剩余的 lilet newActiveIndex = obj.index - 1;    // 删除前一个,通常就是新激活if(newActiveIndex < 0) newActiveIndex = 0;const $newActive = $($tabs[newActiveIndex]);const thisAlias = $newActive.attr('id');const thisUrl = $newActive.attr('data-url');if (!thisAlias || !thisUrl) returnconst alias = thisAlias.split('_')// 解决关闭tab时,url切换不成功问题location.hash = thisUrl;MicroAppHelper.handleMicroAppSwitchByUrl({sessionStorage: $sessionStorage,state: $state,},{url: thisUrl,alias: alias.join('.')},true);}catch(e){}})$(document).off('click', '.layui-tab[lay-filter="contentab"] .layui-tab-title li');// 使用自定义绑定和解绑click事件目的是为了防止事件被触发多次$(document).on('click', '.layui-tab[lay-filter="contentab"] .layui-tab-title li', async function () {const thisUrl = $(this).attr("data-url");const thisAlias = $(this).attr("id");const alias = thisAlias.split('_')MicroAppHelper.handleMicroAppSwitchByUrl({sessionStorage: $sessionStorage,state: $state,},{url: thisUrl,alias: alias.join('.')},true);});}}

上面一共三个事件tab(contentab)、tabDelete(contentab)和click,简单讲解下:
1、tab(contentab)事件:通过设置location.hash = thisUrl,将url改正确,并且通过isMenuTrigger来判断是否已经打开过也就是对应上面的第5条,如果是的话直接重新激活
2、tabDelete(contentab)事件:也一样激活新的,卸载旧的
3、click事件:和上面一样

这样就解决了所有场景的问题。

总结

1、因为我的主应用是angularjs和layui的tab所以需要处理的地方比较多
2、使用renderApp方式来动态加载微前端,不要使用micro-app标签
3、主子应用通过getData和setData来通信,注意name要匹配,否则不生效

引用

micro-app官方文档

http://www.dtcms.com/a/331275.html

相关文章:

  • python bokeh
  • 为什么在函数内部,有时无法访问外部的变量?
  • 从0-1学习Java(三)快速了解字符串、数组、“==“与equals比较
  • 基于STM32的Air780短信发送
  • 【每天一个知识点】生物的数字孪生
  • C++模板特化、分离编译
  • 力扣-295.数据流的中位数
  • InfiniBand 与 RoCE 协议介绍
  • 激光雷达与可见光相机的图像融合
  • C++ vector越界问题完全解决方案:从基础防护到现代C++新特性
  • 【代码随想录day 20】 力扣 538.把二叉搜索树转换为累加树
  • 医疗洁净间的“隐形助手”:富唯智能复合机器人如何重塑手术器械供应链
  • 【大模型微调系列-01】 入门与环境准备
  • 机器翻译:回译与低资源优化详解
  • 高精度组合惯导系统供应商报价
  • Java基础07——基本运算符(本文为个人学习笔记,内容整理自哔哩哔哩UP主【遇见狂神说】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
  • 扩展用例-失败的嵌套
  • Kafka 的消费
  • 学习设计模式《二十二》——职责链模式
  • 微软发布五大AI Agent设计模式 推动企业自动化革新
  • hive加载csv中字段含有换行符的处理方法
  • Java设计模式之《原型模式》--深、浅copy
  • 17 ABP Framework 项目模板
  • Origin绘制正态分布直方图+累积概率图|科研论文图表教程(附数据格式模板)
  • JS的学习6
  • 目标检测-动手学计算机视觉12
  • Redis入门到实战教程,深度透析redis
  • Promise 对象作用及使用场景
  • 实验室的样本是否安全?如何确保实验数据的准确性和可靠性?
  • 京东【自主售后】物流信息获取_影刀RPA源码解读