背景

项目用的组件库是基于Element Plus开发的(后面简称elm),但在使用过程中发现elm的tooltip组件有内存泄漏问题:

file

进一步排查,发现是Vue-3.3版本下,在内置的transition组件中使用v-if会有问题

定位到这个因素,是因为在element-plusplayground中,使用vue3.3.x版本存在内存泄漏,而3.5.13版本不存在

具体排查过程就不放了,还有一篇超长内存泄漏blog正在填坑中

因早就嫌弃elm的tooltip组件使用方式不够优雅,遂把目光转向另一个大名鼎鼎的开源Vue3组件库——PrimeVue

PrimeVue以指令的方式提供了tooltip,比使用组件优雅很多(其实博主已经把elm的tooltip二次封装为指令形式使用了)

正文

问题定位

通过对PrimeVue进行了与elm相同的内存泄漏测试(反复触发tooltip显隐),发现这货的tooltip居然也有内存泄漏问题(如下图),再次印证了草台班子理论。

file

一番排查后,最终定位到导致泄露的代码:

file

可以看出只要不触发resize事件,onWindowResize方法的闭包作用域中会一直持有对el的引用

查一查有没有相关的PR:

file

发现这个问题还没人解决,目测修复也比较简单,故准备给组件库提个PR

梳理逻辑

先来看看PrimeVuetooltip都有啥

file

可以看到里面有跟Vue指令定义差不多的生命周期方法和一部分自己的内部方法methods,图中框起来的部分就是产生内存泄漏的主要代码

其中的BaseTooltipPrimeVueVueObjectDirective基础上做了二次封装及自己的一些扩展的方法

顾名思义,show方法用于元素的显示,hide方法用于元素隐藏,tooltipRemoval则用于移除tooltip元素,展开看一下内部代码

file

tooltipActions代码:

file

到这里我们可以知道:

  1. 在触发指令绑定元素的mouseEnterfocus事件时,会调用show方法显示tooltip
  2. show方法会调用tooltipActions,而tooltipActions中的会创建window上的resize事件监听器,且不触发就不会被释放,进而导致el一直被该监听器方法创建的闭包作用域引用

看的过程中发现两行不对劲代码,准备顺便一起改了:

file

修复思路

首先,我们需要能够从外面引用到监听器,以便在其他地方销毁,先将其从tooltipActions里提出来

file

接下来,需要解决onWindowResizeel参数和this指向的问题,显然应该用bind完成(methods中有bindEvents方法),在bindEvents中添加:

file

再在tooltipRemovalunbindEvents里加上销毁监听器的代码

file

测试效果

构建组件库到本地,link到本地项目测试,反复触发显隐,查看内存快照对比

file

问题解决,效果符合预期

单元测试,启动!

从项目根目录的package.json中可以看到组件库是有单元测试的

file

执行一下单元测试,确保没有产生新bug(内存泄漏的单元测试还没研究怎么写,以后整明白了再帮他们补上)

file

提交PR

提交代码,填写信息,提交PR表单一气呵成

并没有,好久不用Github提代码发现不能用密码登录了,又去建了个token才提上去

file

贴个PR链接,方便以后查(xin)看(shang)

总结

开发过程中用到window.addEventListener方法时需要谨慎,毕竟像PrimeVue这样的知名开源组件库使用它时也难免出现问题。

给开源项目做了点贡献,自己也进步了一点,感觉良好。

Leave a Reply

Your email address will not be published. Required fields are marked *