vue2中keepalive手动清理内存,存在子路由内存无法回收的问题
起因
近期客户经常反馈系统崩溃的问题,尤其是在下午最频繁,经过自己的自测,发现系统tab关闭后内存并没有回收掉,目前我已经处理了,tab页签关闭后,手动清理keep-alive内的缓存,应该不存在内存泄漏的情况,看来还有其他地方的缓存没有清理掉。
定位问题
1.还原场景
公司项目是单页应用,所有的操作都在一个浏览器页签内操作,整个页面是通过Layou+子路由的方式布局的,路由层级达到4级,业务复杂繁琐。需要重新搭建一个纯净项目还原场景
2.写个demo
使用vue-cli创建项目,vue@2.7.9
,vue-router@3.6.5
main.js
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ render: h => h(App), router }).$mount('#app') 复制代码
App.vue
<template> <router-view></router-view> </template> <script> export default { name: "App", }; </script> 复制代码
view/Page1、view/Page2、view/A1、view/A2
<template> <div> Page1 <router-view></router-view> </div> </template> <script> export default { name: "Page1", }; </script> <template> <div> Page2 <router-view></router-view> </div> </template> <script> export default { name: "Page2", }; </script> <template> <div>组件view A1</div> </template> <script> export default { name: "A1", data() { return { a: new Array(20000000).fill(1), //大概80mb }; }, }; </script> <template> <div>组件view A2</div> </template> <script> export default { name: "A2", data() { return { a: new Array(20000000).fill(1), //大概80mb }; }, }; </script> 复制代码
view/Layout.vue
<template> <div> <h1>Layout</h1> <div class="box"> <p>二级路由</p> <router-link :to="{ name: 'A' }">A</router-link> <br /> <router-link :to="{ name: 'B' }">B</router-link> </div> <div class="box"> <p>三级路由</p> <router-link :to="{ name: 'AA' }">Page1</router-link> <br /> <router-link :to="{ name: 'BB' }">Page2</router-link> </div> <div class="box"> <button @click="includeRemove()">清理keepalive缓存</button> <br /> <router-link to="/home">Home</router-link> <br /> </div> <h1>keep-alive</h1> 缓存页面:{{ include }} <keep-alive :include="include"> <router-view ref="alive"></router-view> </keep-alive> </div> </template> <script> export default { name: "Layout", data() { return { include: [], }; }, watch: { '$route'(val) { const name = val.meta.name if (name && !this.include.includes(name)) { this.include.push(name); } } }, methods: { includeRemove() { this.include = []; }, }, mounted() {}, }; </script> <style> .box { margin-bottom: 20px; } </style> 复制代码
router/index.js
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) const router = new Router({ mode: "hash", routes: [ { path: "/", redirect: "/home", component: () => import("../view/Layout.vue"), children: [ { path: "home", component: () => import("../view/Home.vue"), }, ], }, { path: "/a", component: () => import("../view/Layout.vue"), children: [ { path: "a", name: "A", meta: { name: 'A1' }, component: () => import("../view/A1.vue"), }, ], }, { path: "/b", component: () => import("../view/Layout.vue"), children: [ { path: "b", name: "B", meta: { name: 'A2' }, component: () => import("../view/A2.vue"), }, ], }, { path: "/a", component: () => import("../view/Layout.vue"), children: [ { path: "page1", name: "Page1", component: () => import("../view/Page1.vue"), children: [ { path: "a", name: "AA", meta: { name: 'Page1' }, component: () => import("../view/A1.vue"), }, ], }, ], }, { path: "/b", component: () => import("../view/Layout.vue"), children: [ { path: "page2", name: "Page2", component: () => import("../view/Page2.vue"), children: [ { path: "b", name: "BB", meta: { name: 'Page2' }, component: () => import("../view/A2.vue"), }, ], }, ], }, ], }); export default router 复制代码
运行效果
3.重现问题
可以看出,初始情况下,内存使用7.7MB左右
1.点击A、B后,内存占用168MB
2.点击Home,保证在清理缓存时,路由不占用A,B组件,再次点击清理keepalive缓存
手动GC后,发现内存使用变为7.9MB,说明A、B组件成功释放掉了,这个模拟了公司项目前期只有二级路由的情况,那个时候还不存在系统崩溃的问题,这里也刚好印证了。
3.点击Page1、Page2,内存占用是168MB
4.点击Home,再次点击清理keepalive缓存
这个时候就出问题了,内存并没有成功的释放掉,问题找到了
4.分析
首先记录初始情况下内存占用
打开Page1、Page2,切换到Home页面,清理keepalive缓存,记录当前内存快照
从图中可以看出,A1组件还存在,并且是vue-router引用了,nameMap保存了所有的路由信息,这样的话问题就找到了
初始状态下路由信息
打开Page1、Page2,切换到Home页面,清理keepalive缓存后路由信息
只有Page1、Page2的instances.default
是undefined,A1,B1还保留了组件实例
测试另外一种情况,清理keepalive缓存前不切换到Home,这种情况下,内存是可以成功释放掉的。
5.结论
如果是在当前路由关闭tab,然后清理keepalive缓存,内存是可以正常回收的
如果是在其他路由关闭非激活的路由时,二级路由组件可以正常回收,二级以下路由内存回收异常,猜测非激活路由matched内的信息以变更了,毕竟是单例模式,这就说的通,为啥激活的路由移除缓存是正常的了
3.修复问题
1.思路
获取关闭当前tab路由父子关系
通过所有的路由信息,遍历删除相关路由的
instances.default
值
2.具体代码实现
includeRemove() { this.include = []; // 为啥vue-router不开放直接获取nameMap的接口 淦 const routes = this.$router.getRoutes() const nameMap = new Map() for (let index = 0; index < routes.length; index++) { const r = routes[index]; nameMap.set(r.name, r) } // 假设我这边获取到了当前移除的tab页签,具体代码根据具体项目实现 const rList = ['AA', 'BB'] for (let index = 0; index < rList.length; index++) { const name = rList[index]; const r = nameMap.get(name) if (r) r.instances.default = undefined } } 复制代码
代码改造后重新按照流程走了一遍,内存使用情况如下,成功解决问题
总结
一开始根本无从下手,总觉得是客户操作问题,或者是电脑内存太小,一直没有太在意,当更多人反馈这个问题时,才意识到这个问题很普遍,必须得解决掉才行,从开始解决到已解决花了一个星期的时间,解决的代码很简单,但是解决问题的过程很艰辛。
作者:释迦摩尼
链接:https://juejin.cn/post/7169778048374898719