给 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

相关推荐
崔庆才丨静觅17 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606118 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了18 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅18 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅18 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅19 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment19 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅19 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊19 小时前
jwt介绍
前端
爱敲代码的小鱼19 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax