1. 场景说明
我们的前端项目中主要用第三方库vue-infinite-scroll
来实现无限滚动列表。然而,在最近写一个需求时,发现基于vue-infinite-scroll
来实现列表的无限滚动时,会有无法触发滚动事件的问题。这样导致了列表滚动到底部时,无法拉取新的数据。经过查阅第三方库vue-infinite-scroll
的官方文档,以及参考项目中使用到此库的其他地方,发现在用法上没有什么异常。就此基于查看源码去做一个详细的研究。
- 源代码阅读 & 猜想
=============
在node_modules
下面找到了vue-infinite-scroll
库所在的地方,很幸运,打包后的代码没有压缩成完全看不懂的样子,且仅有简短的236
行。这意味着我们理解代码的时间不需要太长。同时,后面我们能够直接修改打包后的文件来进行调试,验证研究中的各种猜想。
按照职能阅读: 尽管代码量不大,但以快速解决问题为目的。我们还是需要避重就轻,只阅读对解决问题有必要的部分即可。通过略读代码,根据函数名划分函数职能,可以帮助我们很好地做到这一点。
- 简单工具类函数:
getScrollTop
,getVisibleHeight
,getElementTop
等,从函数名看出它们的主要职能都是获取列表元素的一些基本属性,加以处理来实现无限滚动。而我们的bug
出现在同一浏览器和网络环境中,理论上不存在因为兼容性问题导致这些函数工作异常的情况。(约100行,都可以略过)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzkaRTMI-1656055287398)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ecc89eef1c9a470fa11bbda1fb3f5ff1~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
- 核心事件/属性绑定函数**
doBind
** : 这个函数里做了许多绑定操作,包括滑动时scroll
事件的回调绑定,以及是否允许列表继续滑动,滑动距离等自定义属性的绑定操作。所以,我们的列表在滑动时,无法触发事件很有可能就是因为在这里面的绑定操作失败了。
- 主函数: 负责把各种职能的函数组织在一起,以实现
vue-infinite-scroll
的功能
在根据源代码的职能进行略读之后,我们重点关注绑定函数doBind
部分。在主函数中,我发现了vue-infinite-scroll
是通过监听组件本身的mounted钩子来执行事件/属性的绑定的。
因此,我们作出猜想:上述遇到的无法触发滚动事件的原因,可能正正是因为监听不到/错过了组件本身的mounted生命周期钩子
3. 解决问题
调试
上文分析到,vue-infinite-scroll
的代码并没有压缩得很难看,我们能够直接修改打包后的文件来进行调试。于是我们在源代码中添加一个简单的断点进行调试:不出所料, 我们的程序并没有进入这个代码块中。
那么问题定位成功了:vue-infinite-scroll
的代码在运行时(监听不到/错过了)组件本身的mounted
生命周期钩子,导致一系列滚动事件都无法进行绑定。或者是,组件本身的mounted钩子触发时,vue-infinite-scroll
的代码仍未初始化完成。
尝试解决
有了生命周期这一线索,我最后把目光放在了一个简单的vue指令:v-if
(我用了这个指令来控制列表的显隐状态)。简单地把v-if
改成了v-show
, bug
就神奇地解决了,列表再次正常滚动,并在滚动到底端的时候触发loadMore
事件。
原因分析
先说说 v-show 和 v-if。 v-show 指令是通过控制元素的css
属性(display
)从而实现显示和隐藏的效果,这也就说无论 v-show 是true
还是false
,元素都会有初始渲染。v-if 就跟 v-show 不太一样了,v-if 状态改变时,是真实进行相关的DOM
操作的(插入和删除),也就是说,当 v-if 是false
时, 元素就不会有初始渲染。
基于上面说的,使用v-if 时:
- 解析到列表元素时,发现
v-if="false"
:列表元素不进行初始渲染,doBind
函数没有执行,无法进行组件mounted
钩子的监听。
2.解析完成,组件挂载完毕(执行了mounted
生命周期钩子),但没有被vue-infinite-scroll
监听到,绑定逻辑代码没有执行。
- 设置
v-if="true"
:列表元素初始渲染,vue-infinite-scroll
绑定逻辑代码doBind
执行,开始监听mounted
生命周期钩子。但组件已完成mounted
,vue-infinite-scroll
错过了mounted
生命周期钩子,无法完成滚动等基础事件的绑定,后续滚动列表等操作无相关响应。
而在使用v-show 时:
-
解析到列表元素时,发现
v-show="false"
:但列表元素依然进行初始渲染,只是通过css
控制其隐藏。此时vue-infinite-scrolld
对列表元素执行doBind
函数,并进行组件mounted
钩子的监听。 -
解析完成,组件挂载完毕(执行了
mounted
生命周期钩子),被vue-infinite-scroll
监听到,执行滑动事件等相应的绑定逻辑代码。 -
设置
v-show="true"
:通过css
设置列表元素显示,doBind
函数中滚动等基础事件的绑定早已完成,后续滚动列表等操作可以正常响应。
4. 拓展: npm包的调试
遇到第三方npm包相关的bug时,调试其代码是非常重要的一步,也做一个拓展总结,请移步 NPM包调试方式总结