Vue3项目里大数据列表卡顿怎么办?用虚拟滚动真的能彻底解决吗?
Vue3虚拟滚动核心原理是什么?为什么能救卡顿的大数据列表? 要搞懂虚拟滚动的价值,先得说清楚大数据列表卡在哪里,假设你要渲染一个有10万条商品数据的电商列表,每条商品卡片占100px高度,那整个页面的DOM节点至少要几十万(包括商品卡片里的文字、图片、按钮这些子元素),浏览器渲染DOM、计算样式、排版、重绘重排的速度,是和DOM节点数量成反比的,1000条以内可能还流畅,1万条就会明显掉帧,滚动时拖拖拉拉,10万条可能连页面都得等半天才能打开。 虚拟滚动的思路很“偷懒”——不管你有多少条数据,我只渲染用户能看到的那一小部分,再加上上下一点点预渲染的“缓冲带”,比如你的视口高度是800px,每条卡片100px,那视口里只能放8条,加上上下各3条缓冲,一共只渲染14条,滚动的时候,我监听滚动事件,计算当前视口应该显示哪几条数据,然后把旧的DOM节点“复用”或者“替换”成新数据的内容,同时通过设置容器的滚动占位高度,让用户感觉列表里还是有10万条数据,滚动条的位置和长度也完全正常,这样一来,不管数据量是1万还是1000万,浏览器要处理的DOM节点始终只有十几个,自然就不会卡了。
Vue3有没有官方推荐的虚拟滚动方案?第三方库怎么选?
Vue3官方文档里并没有内置专门的虚拟滚动组件,不过在VueUse工具库的示例和一些性能优化的最佳实践中,确实把虚拟滚动作为大数据列表的首选解决方案,第三方库的选择要根据你的项目需求来,不能随便挑个下载量最高的就用:
如果你的项目是纯Vue3项目,没有引入额外的UI框架,或者想要完全自定义样式和逻辑,推荐用VueUse里的useVirtualList组合式API,它轻量(压缩后只有几KB)、灵活,是纯逻辑封装,不依赖任何UI库,你可以自己用它搭配div或者ul/li写列表。
如果你的项目已经在用Element Plus(或者Ant Design Vue、Naive UI这些主流Vue3 UI库),那直接用UI库内置的虚拟滚动组件就行,比如Element Plus的el-table-v2(针对表格)、el-virtual-list(针对普通列表),Ant Design Vue的a-table开启virtual属性,Naive UI的n-data-table开启virtual-scroll,这些内置组件已经和UI库的样式、交互完全打通,不需要你自己处理太多细节,比如行高自适应(部分库支持动态行高,这点后面会讲)、滚动到指定行、键盘导航之类的功能都有。
另外还有一些专门的虚拟滚动第三方库,比如vue-virtual-scroller(有Vue2和Vue3两个版本,功能比较全,但可能稍微重一点)、vue-virtual-scroll-list(也支持Vue2/Vue3,社区活跃度很高),不过如果有UI库或者VueUse的组合式API能满足需求,尽量别再引入额外的库,避免增加项目的打包体积。
动态行高的虚拟滚动怎么实现?静态行高和动态行高的区别大吗? 静态行高的虚拟滚动是最简单的,也是最常用的——你提前告诉虚拟滚动组件每条数据占多少高度,不管内容多长,高度都固定,这样计算滚动占位高度、当前视口显示的索引都非常快,性能最好,但如果你的列表内容不一样长,比如有的商品卡片有3张图,有的只有1张图,有的商品标题有10个字,有的有30个字,静态行高就会出现内容被截断、或者留很多空白的问题,这时候就需要动态行高的虚拟滚动。 动态行高的虚拟滚动难点在于,你没办法提前知道每条数据的高度,必须等内容渲染出来之后才能测量,那怎么处理呢?主流的方案是“先渲染后测量再调整”:
- 给每条数据设置一个预估高度,先用预估高度计算滚动占位高度和当前视口显示的索引;
- 渲染出当前视口和缓冲带的内容;
- 监听这些内容的DOM渲染完成事件(比如Vue3的
onMounted或者watchEffect结合DOM尺寸变化),测量它们的实际高度; - 更新每条数据的高度缓存,重新计算滚动占位高度和当前视口显示的索引,可能会稍微调整一下滚动条的位置(如果不调整的话,滚动时会出现跳跃的情况);
- 下次滚动到这条数据的时候,直接用缓存里的实际高度,不用再测量了。 动态行高和静态行高的区别还是挺大的:静态行高的性能是最好的,几乎没有额外的开销;动态行高因为要测量DOM、更新缓存、调整滚动条,会有一点点额外的性能损耗,但只要预估高度设置得合理(和大部分数据的实际高度差不多),这种损耗几乎可以忽略不计,用户体验会比静态行高好很多。
Vue3虚拟滚动有哪些常见的坑?怎么避免?
用虚拟滚动的时候,新手很容易踩一些坑,比如列表滚动时出现空白、内容闪烁、滚动条跳跃、表格排序筛选失效之类的,下面说几个最常见的坑和对应的解决办法:
第一个坑是列表滚动时出现大片空白,尤其是快速滚动的时候,这种情况一般是因为缓冲带设置得太小了——缓冲带的作用是提前渲染上下几屏的内容,避免快速滚动时用户看到空白,VueUse里的useVirtualList默认的缓冲带是3(也就是上下各3条),如果你的卡片比较小,或者快速滚动的速度比较快,可以把缓冲带设置得大一点,比如5或者10。
第二个坑是内容闪烁,这种情况一般是因为DOM节点没有被复用,每次滚动都在销毁和创建新的DOM节点,解决办法是用Vue3的v-for结合key属性,但这里的key不能用索引,必须用数据的唯一标识(比如商品的id),这样Vue3才能正确地复用DOM节点,减少销毁和创建的开销,同时避免内容闪烁。
第三个坑是滚动条跳跃,这个主要出现在动态行高的虚拟滚动里,刚才说过,解决办法是设置合理的预估高度,尽量让预估高度和实际高度接近,测量DOM高度的时候要尽量准确,不要用offsetHeight这种可能会受滚动条、边框、外边距影响的属性,最好用getBoundingClientRect().height或者ResizeObserver来监听DOM尺寸变化,实时更新高度缓存。
第四个坑是表格排序筛选失效,这个主要出现在用UI库内置虚拟滚动组件的时候,比如Element Plus的el-table-v2,这种情况一般是因为你把排序筛选逻辑放在了DOM渲染之后,而虚拟滚动组件只渲染了当前视口的内容,所以排序筛选只会针对当前视口的几条数据,而不是整个大数据列表,解决办法是把排序筛选逻辑放在数据层面,也就是在把数据传给虚拟滚动组件之前,先对整个大数据列表进行排序筛选,然后再把处理后的数据传给虚拟滚动组件。
第五个坑是图片懒加载失效,因为虚拟滚动只渲染当前视口和缓冲带的内容,当你滚动到之前没渲染过的内容时,图片可能还没来得及加载,就已经被销毁了(或者被移出视口了),解决办法是把图片懒加载的阈值设置得大一点,或者用专门的虚拟滚动图片懒加载库,或者干脆不懒加载缓冲带里的图片,只懒加载缓冲带之外的图片。
Vue3虚拟滚动真的能彻底解决大数据列表卡顿吗?有没有局限性? 刚才说了那么多虚拟滚动的好处,那它真的能彻底解决大数据列表卡顿吗?其实不能说“彻底”,只能说“在大部分情况下能完美解决”,它还是有一些局限性的: 第一个局限性是它只能优化DOM渲染的开销,如果你的大数据列表里的每条数据都有很复杂的逻辑(比如每条数据都要进行大量的计算、调用很多接口),那即使DOM节点很少,计算逻辑的开销也会让页面卡顿,这时候你需要优化的是数据层面的逻辑,比如用Web Worker来处理复杂的计算、用防抖节流来减少接口调用、用缓存来存储已经处理过的数据。 第二个局限性是它不适合所有类型的列表,比如列表项之间有复杂的依赖关系(比如折叠面板,展开一个面板会影响其他面板的高度)、或者需要同时选中多个列表项并进行批量操作(比如跨页选中,虚拟滚动只渲染当前视口的内容,所以跨页选中需要自己处理数据的选中状态)。 第三个局限性是动态行高的虚拟滚动可能会有一点点滚动条跳跃的问题,虽然设置合理的预估高度可以尽量避免,但如果数据的实际高度差异特别大(比如有的只有10px,有的有1000px),滚动条跳跃的问题还是会比较明显。
Vue3虚拟滚动是目前解决大数据列表卡顿最有效的方案之一,只要你的项目符合它的适用场景,用它就能带来非常明显的性能提升,如果你的项目里有大数据列表,赶紧试试吧!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


