Code前端首页关于Code前端联系我们

Vue3 虚拟列表是什么?怎么实现高效渲染长列表?

terry 2小时前 阅读数 4 #Vue

咱做前端项目时,要是遇到几百几千条数据的长列表渲染,普通 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 设 heightoverflow: auto),这个容器就是用户能看到的“窗口”。

(2)计算单条数据的高度(固定/动态)

  • 固定高度:比如每条订单信息都是 60px 高,计算简单,后面全靠数学公式推导。
  • 动态高度:每条高度不固定(比如朋友圈动态,有的带图有的纯文字),这时候得 实时测量已渲染项的高度(用 ResizeObserveroffsetHeight),再存起来给后续计算用。

(3)算清楚“该渲染哪一段数据”

滚动时,根据滚动条的位置,算出当前可视区域对应的 起始索引结束索引,举个固定高度的例子:
假设容器高度 300px,单条 60px,那可视区域能装 5 条(300 / 60 = 5),滚动条往下滑了 120px,那起始索引就是 120 / 60 = 2,结束索引是 2 + 5 = 7,所以只渲染索引 2 到 6 的数据(数组从 0 开始)。

(4)处理“偏移量”让滚动顺滑

因为只渲染部分数据,列表容器的总高度得保持不变(不然滚动条会乱跳),但实际 DOM 只有可视部分,这时候要给一个 “偏移容器”,用 transformmargin 把可视内容“推”到正确的滚动位置,比如上面例子,起始索引是 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 渲染完再测高度,所以在 onMountednextTick 里处理更稳。

有没有现成的 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 ListenersaddEventListener({ passive: true }))减少浏览器阻塞。

(2)减少响应式开销

Vue3 的响应式是“递归监听”,大量数据用 reactive 会有性能问题,可以用 shallowRef 存数据(只监听外层变化),或者把非响应式数据放普通对象里,减少依赖追踪。

(3)动态高度的缓存策略

动态高度场景下,每次测量后把高度存在数组里,滚动时直接读缓存,别重复测量,还能搞个“预渲染”区域,提前测量即将进入可视区的项高度,减少滚动时的计算压力。

(4)样式与布局优化

给列表项加 will-change: transform ,让浏览器提前准备渲染;避免用复杂的 CSS 选择器,减少样式计算时间。

实际项目里虚拟列表容易踩哪些坑?

(1)滚动时“白屏”或内容错位

原因一般是 索引计算错误(比如没考虑容器的 paddingborder),或者偏移量计算错了,解决方法:把容器的内边距算进滚动距离,偏移量公式多检查。

(2)动态高度“闪烁”或高度不准

因为测量高度是异步的,滚动时可能还没拿到真实高度,导致布局乱跳,可以先给个“占位高度”,等测量完再替换,或者用 ResizeObserver 实时监听高度变化。

(3)快速滚动时卡顿

比如用户用鼠标滚轮快速滑,scroll 事件没及时处理,导致可视区数据没及时更新,出现空白,这时候要优化滚动事件的节流策略,或者用 requestAnimationFrame 代替 setTimeout ,让渲染更跟手。

(4)数据更新后列表不同步

比如异步请求到新数据,虚拟列表没触发重新计算,要确保数据变化时,触发起止索引和可视数据的重新计算(用 watch 监听数据长度变化)。

现在再回头看,Vue3 虚拟列表核心就是“只渲染可见区,减少 DOM 压力”,不管是自己封装还是用社区组件,理解了可视区域计算、高度处理、偏移量这些核心逻辑,再结合 Vue3 的 Composition API 做逻辑复用,长列表性能问题基本就解决了,要是你项目里有表格、下拉选择器这类大数据场景,赶紧把虚拟列表安排上,用户体验直接起飞~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门