给 Ant Design Vue 装上"涡轮增压":虚拟列表封装实践

前言

如果你用过 Ant Design Vue 的 Table 组件渲染过上万条数据,你一定体验过那种"浏览器在思考人生"的感觉。页面卡顿、滚动掉帧,仿佛在对你说:"兄弟,我真的尽力了。"

问题的根源很简单:Ant Design Vue 的 Table 组件并不支持虚拟列表。当你塞给它 10 万条数据时,它会老老实实地把这 10 万个 DOM 节点全部渲染出来。浏览器:我谢谢你啊。

于是,这个项目诞生了------基于 Ant Design Vue 二次封装,让它也能享受虚拟列表的"涡轮增压"。

什么是虚拟列表?

简单来说,虚拟列表就是一种"障眼法"。用户看起来在滚动一个包含 10 万条数据的列表,但实际上浏览器只渲染了可视区域内的那几十条数据。当你滚动时,组件会动态计算应该显示哪些数据,然后快速替换 DOM。

这就像是火车车窗外的风景:你坐在车厢里,透过窗户看到的永远只是窗外那一小片景色,但随着火车移动,窗外的景色不断变化,给你一种穿越了整片大地的感觉。虚拟列表也是如此,可视区域就是那扇窗户,数据就是窗外的风景。

核心实现:站在巨人的肩膀上

这个项目的核心是 @vueuse/core 提供的 useVirtualList hook。VueUse 是一个优秀的 Vue 组合式 API 工具集,而 useVirtualList 就是其中专门用来实现虚拟列表的工具。

让我们看看关键代码:

vue 复制代码
const { list, containerProps, wrapperProps } = useVirtualList(
  computed(() => props.dataSource),
  {
    itemHeight: props.rowHeight,
    overscan: 10,
  },
)

必要参数解析

1. 数据源(第一个参数)

这里传入的是一个响应式的数据源。 computed(() => props.dataSource),当父组件传入的数据变化时,虚拟列表会自动更新。

2. itemHeight(行高)

这是虚拟列表的"灵魂参数"。它告诉 useVirtualList:"嘿,我的每一行有多高。"有了这个信息,它才能准确计算出:

  • 可视区域能显示多少行
  • 滚动到某个位置时应该显示哪些数据
  • 整个列表的总高度是多少

重要提示:这个值必须尽可能准确。如果你设置的行高和实际渲染的行高不一致,滚动时就会出现"跳跃"或"错位"的问题。在我们的实现中,默认值是 55px,这是 Ant Design Vue Table 的默认行高。

3. overscan(预渲染数量)

这是一个性能优化参数。它的意思是:"除了可视区域的数据,我还要多渲染上下各 10 条数据。"

为什么要这么做?想象一下,如果只渲染可视区域的数据,当用户快速滚动时,新的数据可能来不及渲染,就会出现短暂的空白。通过预渲染一些数据,可以让滚动更加流畅。

当然,这个值也不能设置太大,否则就失去了虚拟列表的意义。10 是一个比较平衡的值。

返回值解析:虚拟列表的"三剑客"

useVirtualList 返回了三个关键对象,它们各司其职,共同完成虚拟列表的魔法。

1. list - 数据的"精选集"

这是当前应该渲染的数据列表。注意,这不是完整的数据源,而是经过计算后,当前可视区域(加上 overscan)应该显示的数据。

数据结构如下:

javascript 复制代码
[
  { data: 原始数据, index: 在完整列表中的索引 },
  { data: 原始数据, index: 在完整列表中的索引 },
  ...
]

比如你有 10 万条数据,但 list 可能只包含 20-30 条数据,这就是虚拟列表的核心优势。

2. containerProps - 滚动的"指挥官"

这是绑定到外层容器的属性对象,它的结构大概是这样的:

javascript 复制代码
{
  ref: containerRef,           // 容器的引用
  onScroll: () => {            // 滚动事件监听
    calculateRange();          // 重新计算应该显示哪些数据
  },
  style: {
    overflowY: "auto"          // 允许垂直滚动
  }
}

核心作用

  • ref:VueUse 需要获取容器的 DOM 引用,以便计算可视区域的大小和滚动位置
  • onScroll :这是虚拟列表的"心跳"。每次滚动时,它会触发 calculateRange() 函数,重新计算当前应该显示哪些数据
  • style:确保容器可以滚动

在我们的实现中,这样使用:

vue 复制代码
<div v-bind="containerProps" class="virtual-scroll-container">
  <!-- 内容 -->
</div>

通过 v-bind 直接绑定,所有的属性和事件监听都会自动应用到容器上。

3. wrapperProps - 高度的"撑杆"

这是绑定到内层包裹元素的属性对象,它的结构是这样的:

javascript 复制代码
{
  width: "100%",
  height: "5500000px",    // 注意这个惊人的高度!
  marginTop: "0px"        // 动态调整,实现滚动偏移
}

为什么高度这么夸张?

假设你有 10 万条数据,每条高度 55px,那么完整列表的总高度就是:

ini 复制代码
100000 × 55 = 5,500,000px = 5500000px

这个高度是计算出来的"虚拟高度"。虽然实际只渲染了几十条数据,但通过设置这个巨大的高度,可以让滚动条的长度和滚动范围与真实的 10 万条数据保持一致。

marginTop 的妙用

当你滚动到列表中间时,比如滚动到第某条数据,marginTop 会动态调整为正值(比如 2915px),这样可以:

  1. 让当前渲染的数据显示在正确的位置
  2. 保持滚动条的位置准确

这就像是一个"移动的窗口",窗口内的内容在不断变化,但窗口的位置始终准确。通过动态调整 marginTop,VueUse 将实际渲染的少量数据"推"到了应该显示的位置,从而实现了虚拟滚动的效果。

在我们的实现中:

vue 复制代码
<div v-bind="wrapperProps">
  <a-table ... />
</div>

这个包裹层撑起了整个虚拟列表的"骨架",让浏览器以为真的有 10 万条数据在那里。

封装

1. 容器高度的控制

虚拟列表需要一个固定高度的容器来触发滚动。我们通过 CSS 变量动态绑定容器高度:

vue 复制代码
<style scoped>
.virtual-scroll-container {
  height: v-bind(containerHeight + 'px');
  overflow-y: auto;
  /* 其他样式... */
}
</style>

这样,父组件可以通过 container-height prop 灵活控制可视区域的高度。

2. 插槽的完整透传

为了保持 Ant Design Vue Table 的灵活性,我们需要把所有插槽都透传给内部的 Table 组件:

vue 复制代码
<template v-for="(_, name) in $slots" #[name]="slotData">
  <slot :name="name" v-bind="slotData || {}"></slot>
</template>

这样,父组件可以像使用原生 Table 一样使用自定义列、自定义单元格等功能。

3. 必须注意的细节

在使用这个组件时,有一个容易被忽略的细节:表格列的 ellipsis 必须设置为 true

为什么?因为虚拟列表的行高是固定的,如果内容超出了单元格,就会撑高行高,导致计算错位。设置 ellipsis: true 可以确保内容超出时显示省略号,而不是换行。

javascript 复制代码
const columns = [
  { title: '类型', key: 'type', width: 400, ellipsis: true },
  // ...
]

性能对比

在测试页面中,我们生成了 10 万条数据

javascript 复制代码
const tableData = ref(generateMockData(100000))

如果用原生的 Ant Design Vue Table 渲染,浏览器会直接"去世"。但使用虚拟列表封装后,滚动依然丝滑流畅。

这就是虚拟列表的魅力:无论数据有多少,实际渲染的 DOM 节点永远只有那么几十个。

使用方式

使用这个组件非常简单,和原生 Table 几乎一样:

vue 复制代码
<v-table
  :data-source="tableData"
  :columns="columns"
  :row-height="55"
  :container-height="600"
  row-key="id"
/>

唯一的区别是多了两个参数:

  • row-height:行高,必须准确
  • container-height:容器高度,决定可视区域大小

总结

这个项目的核心思路很简单:

  1. 借助 VueUse 的 useVirtualList 实现虚拟列表逻辑
  2. 将虚拟列表的数据转换为 Ant Design Vue Table 需要的格式
  3. 通过 props 和插槽保持组件的灵活性

项目地址:ant-virtual-table

相关推荐
智商偏低3 小时前
JSEncrypt
javascript
谎言西西里3 小时前
零基础 Coze + 前端 Vue3 边玩边开发:宠物冰球运动员生成器
前端·coze
+VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑4 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程
GIS之路4 小时前
GDAL 实现数据空间查询
前端
OEC小胖胖4 小时前
01|从 Monorepo 到发布产物:React 仓库全景与构建链路
前端·react.js·前端框架
2501_944711434 小时前
构建 React Todo 应用:组件通信与状态管理的最佳实践
前端·javascript·react.js
困惑阿三5 小时前
2025 前端技术全景图:从“夯”到“拉”排行榜
前端·javascript·程序人生·react.js·vue·学习方法
苏瞳儿5 小时前
vue2与vue3的区别
前端·javascript·vue.js
weibkreuz6 小时前
收集表单数据@10
开发语言·前端·javascript