人人都会AI编程

虚拟列表实现:vue-virtual-scroller

更新时间:2026-07-09

当列表数据达到千条甚至万条时,直接把所有 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: 400pxmax-height + 内部滚动),否则无法计算可视区域。
  • 如果项内有异步加载内容(如图片),建议用 min-item-size 或搭配 DynamicScroller 来适应高度变化。
  • RecycleScroller 要求项高度基本一致;高度差异大时用 DynamicScroller,但会有略微额外的测量开销。

一个真实的数值:一个 10000 行、每行包含头像和描述文本的列表,使用 vue-virtual-scroller 后,内存占用从 ~500MB 降到 ~30MB,滚动帧率稳定在 60fps。这就是虚拟列表在“看不见的地方”省下的成本。