当列表数据达到千条甚至万条时,直接把所有 DOM 节点都渲染出来,页面会变得极其卡顿,滚动也不再流畅。原因是浏览器需要维护成千上万个 DOM 节点,每次重排重绘的开销都会成倍增加。
虚拟列表的核心思路很简单:只渲染用户当前可视区域内的那几条数据,其余部分用占位元素撑开滚动高度。这样无论数据总量有多少,实际渲染的 DOM 节点始终维持在可控范围内(通常几十个),滚动性能几乎不受数据总量影响。
在 Vue 生态中,vue-virtual-scroller 是处理这一场景最成熟、最常用的库。
安装与引入
npm install vue-virtual-scroller
在入口文件中全局注册:
import { createApp } from 'vue'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
const app = createApp(App)
app.use(VueVirtualScroller)
也可以按需在组件内单独引入。
基础用法:虚拟长列表
最常用的组件是 RecycleScroller,它会回收离开可视区域的 DOM 节点并复用。
<template>
<RecycleScroller
class="scroller"
:items="list"
:item-size="50"
key-field="id"
v-slot="{ item }"
>
<div class="item">
{{ item.name }}
</div>
</RecycleScroller>
</template>
<script setup>
import { ref } from 'vue'
const list = ref(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item #${i}`
}))
)
</script>
<style scoped>
.scroller {
height: 400px; /* 必须指定容器高度 */
}
.item {
height: 50px;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
padding: 0 12px;
}
</style>
:items— 传入完整数据数组(包含全部 10000 条)。:item-size— 每一项的固定高度,这是性能优化的关键(可用:min-item-size配合size-field支持不定高)。key-field— 每条数据的唯一标识,用于高效的重用与缓存。v-slot— 渲染每项的模板,item就是当前项的数据对象。
实际效果:滚动时只会创建大约 10~15 个 DOM 节点(取决于容器高度和 item-size),页面上始终只有这些节点存在,10000 条数据的滚动和 30 条数据一样流畅。
处理不定高度
如果列表项高度不固定,可以改用 DynamicScroller 组件,并让每一项提供自己的高度信息。
<template>
<DynamicScroller
class="scroller"
:items="list"
:min-item-size="60"
key-field="id"
v-slot="{ item, index, active }"
>
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.content]"
:data-index="index"
>
<div class="item" v-text="item.content" />
</DynamicScrollerItem>
</DynamicScroller>
</template>
关键点:
min-item-size— 给一个预估的最小高度,帮助快速计算占位。size-dependencies— 告诉组件哪些数据变化时,该项高度可能需要重新计算。- 子项必须包裹在
DynamicScrollerItem中,它会自动测量真实高度并更新占位。
它做了什么,我们不需要做什么
- 自动回收节点:滚动出视口的 DOM 不会被销毁,而是被清理内容后移到下面即将出现的区域,节省大量创建/销毁开销。
- 计算可见区域:根据滚动位置、容器高度、项高度自动算出当前应该渲染哪几条数据。
- 保持滚动位置:数据更新后不会导致滚动条跳动,保证体验平稳。
什么时候该用它
- 列表数据超过 200 条 时就应该考虑虚拟滚动。
- 聊天消息列表、日志流、商品搜索结果、行政区划选择器等场景尤为常见。
- 如果列表项包含图片、复杂布局,虚拟滚动能明显降低内存占用和卡顿。
注意事项
- 容器必须设置明确的高度(如
height: 400px或max-height+ 内部滚动),否则无法计算可视区域。 - 如果项内有异步加载内容(如图片),建议用
min-item-size或搭配DynamicScroller来适应高度变化。 RecycleScroller要求项高度基本一致;高度差异大时用DynamicScroller,但会有略微额外的测量开销。
一个真实的数值:一个 10000 行、每行包含头像和描述文本的列表,使用 vue-virtual-scroller 后,内存占用从 ~500MB 降到 ~30MB,滚动帧率稳定在 60fps。这就是虚拟列表在“看不见的地方”省下的成本。