Vue3 虚拟列表是什么?怎么实现高效渲染长列表?
咱做前端项目时,要是遇到几百几千条数据的长列表渲染,普通 v-for
一整个渲染出去,浏览器分分钟卡成 PPT,这时候 Vue3 的虚拟列表就能派上大用场!但好多同学对虚拟列表是啥、咋在 Vue3 里实现,还有实际开发要注意啥细节,一堆疑问,今天就用问答形式,把 Vue3 虚拟列表的关键知识点拆明白,帮你搞定长列表性能难题~
Vue3 里的虚拟列表到底是什么?
你可以把虚拟列表理解成 “只渲染用户当前能看到的列表部分,看不见的先藏起来” 的技术,打个比方:朋友圈刷到几百条动态,手机屏幕就那么大,每次只显示满屏的十几条,剩下的几百条其实没真正渲染成 DOM 节点,等你滚动时再替换成新的可见内容。
普通列表用 v-for
把所有数据都渲染成 DOM,数据一多(5000+),DOM 节点“爆炸”,浏览器渲染、计算样式、布局全变慢,页面就卡,虚拟列表的核心逻辑是 计算“可视区域”该显示哪一段数据,只渲染这一段对应的 DOM,DOM 数量从几千降到几十,性能自然起飞。
为什么长列表场景要选虚拟列表?
先看普通列表的痛点:假设做个外卖订单列表页,后台返回 1 万条历史订单,用 v-for
循环渲染,光 DOM 节点就 1 万+,浏览器要花大量时间解析 DOM、计算样式、绘制页面,手机端甚至会直接卡死。
虚拟列表解决的就是这些问题:
- 性能暴增:DOM 节点少了,内存占用、渲染时间、脚本执行时间全下降,1 万条数据,虚拟列表只渲染 30 条左右(取决于屏幕高度和单条高度),性能差距是“数量级”的。
- 体验更顺:滚动时不会因为大量 DOM 操作“掉帧”,滑动丝滑得像德芙广告。
- 场景适配广:像表格、下拉选项、聊天记录、无限滚动加载这些场景,只要数据量大,用虚拟列表都能“救场”。
Vue3 实现虚拟列表的核心思路是啥?
不管是自己封装还是用现成组件,核心逻辑绕不开这几步:
(1)确定“可视区域”的范围
先给列表套个固定高度的容器(比如用 CSS 设 height
和 overflow: auto
),这个容器就是用户能看到的“窗口”。
(2)计算单条数据的高度(固定/动态)
- 固定高度:比如每条订单信息都是 60px 高,计算简单,后面全靠数学公式推导。
- 动态高度:每条高度不固定(比如朋友圈动态,有的带图有的纯文字),这时候得 实时测量已渲染项的高度(用
ResizeObserver
或offsetHeight
),再存起来给后续计算用。
(3)算清楚“该渲染哪一段数据”
滚动时,根据滚动条的位置,算出当前可视区域对应的 起始索引 和 结束索引,举个固定高度的例子:
假设容器高度 300px,单条 60px,那可视区域能装 5 条(300 / 60 = 5
),滚动条往下滑了 120px,那起始索引就是 120 / 60 = 2
,结束索引是 2 + 5 = 7
,所以只渲染索引 2 到 6 的数据(数组从 0 开始)。
(4)处理“偏移量”让滚动顺滑
因为只渲染部分数据,列表容器的总高度得保持不变(不然滚动条会乱跳),但实际 DOM 只有可视部分,这时候要给一个 “偏移容器”,用 transform
或 margin
把可视内容“推”到正确的滚动位置,比如上面例子,起始索引是 2,偏移量就是 2 * 60 = 120px
,用 transform: translateY(120px)
把可视内容定位到对应位置,用户滚动时就感觉和普通列表一样。
Vue3 写虚拟列表得注意哪些技术细节?
(1)固定高度场景:逻辑简单但要精准
自己封装的话,步骤大概是:
- 用
ref
获取列表容器和单条项的 DOM,拿到容器高度和单条高度。 - 监听容器的
scroll
事件,计算滚动距离scrollTop
。 - 起始索引 =
Math.floor(scrollTop / 单条高度)
- 结束索引 = 起始索引 + 可视项数(容器高度 / 单条高度,向上取整)
- 用
computed
过滤出数据中索引在[起始, 结束)
之间的子集,渲染到页面。 - 偏移量用
transform: translateY(起始索引 * 单条高度)
要注意 滚动事件的性能:scroll
触发太频繁,得用“节流”(lodash 的 throttle
),不然每次滚动都疯狂计算,反而卡。
(2)动态高度场景:测量与缓存是关键
动态高度麻烦在每条高度不固定,得先渲染一部分,测量高度后存起来,后续滚动时复用,步骤:
- 初始化一个高度数组,存每个项的真实高度,初始可以设个“预估高度”(60px)。
- 用
ResizeObserver
监听已渲染项的高度变化,更新高度数组。 - 滚动时,计算总高度(高度数组累加),再根据
scrollTop
算出当前在哪个“区间”,确定起始和结束索引。 - 因为高度不固定,可视项数得动态算,可能还要处理“回滚时已测量的高度复用”,避免重复计算。
这里容易踩的坑是 测量时机:得等 DOM 渲染完再测高度,所以在 onMounted
或 nextTick
里处理更稳。
有没有现成的 Vue3 虚拟列表组件可以参考?
社区里成熟的方案不少,vue-virtual-scroller
(支持 Vue3),它把固定高度、动态高度、无限滚动这些场景都封装好了,甚至能处理树结构列表,用法大概是:
<template> <VirtualScroller :items="bigData" :item-size="60"> <template #default="{ item }"> <div>{{ item }}</div> </template> </VirtualScroller> </template>
如果想自己封装,结构大概分三层:
- 最外层是固定高度的滚动容器(负责
scroll
事件)。 - 中间层是“偏移容器”(用
transform
控制位置)。 - 最内层是“可见项容器”(只渲染当前可视的数据子集)。
自己写的话,逻辑重点在 数据切片(用 computed
根据起止索引过滤数据)和 滚动事件处理(节流 + 索引计算)。
虚拟列表在 Vue3 里遇到性能瓶颈咋优化?
就算用了虚拟列表,写不好也会有小卡顿,这些优化点要记好:
(1)滚动事件“减负”
scroll
事件默认触发太频繁,用“节流”(50ms 一次),或者用 Passive Event Listeners
(addEventListener({ passive: true })
)减少浏览器阻塞。
(2)减少响应式开销
Vue3 的响应式是“递归监听”,大量数据用 reactive
会有性能问题,可以用 shallowRef
存数据(只监听外层变化),或者把非响应式数据放普通对象里,减少依赖追踪。
(3)动态高度的缓存策略
动态高度场景下,每次测量后把高度存在数组里,滚动时直接读缓存,别重复测量,还能搞个“预渲染”区域,提前测量即将进入可视区的项高度,减少滚动时的计算压力。
(4)样式与布局优化
给列表项加 will-change: transform
,让浏览器提前准备渲染;避免用复杂的 CSS 选择器,减少样式计算时间。
实际项目里虚拟列表容易踩哪些坑?
(1)滚动时“白屏”或内容错位
原因一般是 索引计算错误(比如没考虑容器的 padding
、border
),或者偏移量计算错了,解决方法:把容器的内边距算进滚动距离,偏移量公式多检查。
(2)动态高度“闪烁”或高度不准
因为测量高度是异步的,滚动时可能还没拿到真实高度,导致布局乱跳,可以先给个“占位高度”,等测量完再替换,或者用 ResizeObserver
实时监听高度变化。
(3)快速滚动时卡顿
比如用户用鼠标滚轮快速滑,scroll
事件没及时处理,导致可视区数据没及时更新,出现空白,这时候要优化滚动事件的节流策略,或者用 requestAnimationFrame
代替 setTimeout
,让渲染更跟手。
(4)数据更新后列表不同步
比如异步请求到新数据,虚拟列表没触发重新计算,要确保数据变化时,触发起止索引和可视数据的重新计算(用 watch
监听数据长度变化)。
现在再回头看,Vue3 虚拟列表核心就是“只渲染可见区,减少 DOM 压力”,不管是自己封装还是用社区组件,理解了可视区域计算、高度处理、偏移量这些核心逻辑,再结合 Vue3 的 Composition API 做逻辑复用,长列表性能问题基本就解决了,要是你项目里有表格、下拉选择器这类大数据场景,赶紧把虚拟列表安排上,用户体验直接起飞~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。