TanStack Virtual 入门:长列表虚拟滚动
一万条数据的列表直接渲染,页面就死了。虚拟滚动的思路:只渲染看得见的那 20 条。
为什么需要虚拟滚动
DOM 不是免费的。每个列表项是一堆 DOM 节点,1 万条 × 每条 20 个节点 = 20 万节点,浏览器的布局、绘制、内存全面告急:滚动卡顿、点击延迟、手机直接发烫。
但有个关键洞察:用户的屏幕同时只能看到十几条。那 1 万条里 99.8% 的渲染纯粹是浪费。虚拟滚动的方案:
只渲染视口内 + 上下缓冲的条目(比如 20 条)
其余的用两个占位 div 撑起总高度
滚动时监听 scrollTop,动态换掉渲染窗口里的条目
对浏览器来说,这个列表永远是 20 个节点;对用户来说,和完整渲染毫无区别。万条如二十条。
TanStack Virtual 是什么
TanStack Virtual(前身 react-virtual,现支持 Vue/Svelte/Solid)是虚拟滚动的标准库实现,纯逻辑无 UI——它给你的是「当前应该渲染哪些条目」的计算结果,DOM 长什么样完全由你控制。本站前端用的是 Vue 适配包:
pnpm add @tanstack/vue-virtual
最小示例:定高列表
定高(每条高度一致)是最简单的场景:
<script setup>
import { ref } from 'vue';
import { useVirtualizer } from '@tanstack/vue-virtual';
// 假设有一万条数据
const items = ref(fetchTenThousandItems());
const parentRef = ref(null);
const virtualizer = useVirtualizer({
count: items.value.length,
getScrollElement: () => parentRef.value, // 滚动容器
estimateSize: () => 50, // 每条高度 50px
overscan: 5, // 视口外上下各多渲染 5 条
});
</script>
<template>
<!-- 外层:真实滚动容器,固定高度 -->
<div ref="parentRef" class="list-container">
<!-- 内层:撑起总高度的占位 -->
<div :style="{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }">
<!-- 只渲染虚拟窗口内的条目 -->
<div
v-for="row in virtualizer.getVirtualItems()"
:key="items[row.index].id"
:style="{
position: 'absolute',
top: 0,
transform: `translateY(${row.start}px)`,
height: `${row.size}px`,
width: '100%',
}"
>
{{ items[row.index].title }}
</div>
</div>
</div>
</template>
<style>
.list-container { height: 600px; overflow-y: auto; }
</style>
三层结构是所有虚拟滚动的通用形:滚动容器(真实滚动)→ 总高占位(撑出滚动条长度)→ 绝对定位的窗口条目。virtualizer.getVirtualItems() 返回的永远是当前该渲染的那一小撮,带 index 和 start(该条应在的纵向偏移)。
动态高度:measureElement
条目高度不固定(聊天记录、文章列表)时,用测量模式:
<div
v-for="row in virtualizer.getVirtualItems()"
:key="row.key"
:ref="(el) => virtualizer.measureElement(el)"
:style="{ transform: `translateY(${row.start}px)` }"
>
<div :class="内容高度不定">{{ items[row.index].content }}</div>
</div>
measureElement 会在每条渲染后实测其真实高度并回写虚拟器,滚动定位随之修正。首次滚动到深处可能有轻微跳动——高度是从估算逐步校准到实测的,这是动态高度虚拟滚动的固有代价,可接受。
滚动控制
// 跳到第 500 条(搜索定位、回到顶部等场景)
virtualizer.scrollToIndex(500, { align: 'start' });
这个 API 配合「搜索命中定位」「锚点跳转」非常好用——传统长列表想跳到第 N 条,只能先全量渲染再 scrollTop,虚拟列表下是一个函数调用。
本站的场景
本站后台用 @tanstack/vue-virtual 做长列表虚拟滚动,典型的如消息记录、日志流这类「条数会持续增长」的数据面板。配套要点:滚动容器要固定高度 + overflow: auto,条目 key 必须稳定(用数据 id,别用 index)——key 不稳定会导致滚动时组件复用错乱、输入框内容串行。
什么时候不需要它
虚拟滚动有成本:结构复杂化、动态高度微跳、与窗口化结合的动画(如 FLIP)会失效。所以:
- 条目少于几百个 → 直接渲染,别过度工程;
- 分页已经天然截断(每页 20 条)→ 不需要;
- 无限滚动加载 + 条数会涨到几千上万 → 该上了。
小结
虚拟滚动的本质是用「渲染窗口」替代「全量渲染」,用空间换时间的反向操作。TanStack Virtual 把窗口计算这件事做到了框架无关的极致,UI 完全自持。记住三层结构和 measureElement 两个核心,万级列表的流畅渲染就有了。