简介
TanStack Virtual 是一个无头 UI 工具,用于在 JS/TS、React、Vue、Svelte、Solid、Lit 和 Angular 中虚拟化长列表元素。它不是一个组件,因此不会为您提供或渲染任何标记或样式。虽然这需要您自己提供一些标记和样式,但您将完全掌控您的样式、设计和实现。
虚拟器 (The Virtualizer)
TanStack Virtual 的核心是 Virtualizer。虚拟器可以在垂直(默认)或水平轴上定位,这使得通过结合两种轴配置可以实现垂直、水平甚至网格状的虚拟化。
这是一个使用 Vue 中的 TanStack Virtual 虚拟化 div 内长列表的快速示例:
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'
// 可滚动容器的引用
const parentRef = ref<HTMLElement | null>(null)
// 虚拟滚动实例
const rowVirtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.value,
estimateSize: () => 35,
})
</script>
<template>
<div
ref="parentRef"
style="height: 400px; overflow: auto;"
>
<div
:style="{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative'
}"
>
<div
v-for="virtualRow in rowVirtualizer.getVirtualItems()"
:key="virtualRow.key"
:style="{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}"
>
Row {{ virtualRow.index }}
</div>
</div>
</div>
</template>
代码详解:
// 虚拟滚动实例
const rowVirtualizer = useVirtualizer({
count: 10000, // 支持 1 万行的虚拟滚动
getScrollElement: () => parentRef.value, // 告诉虚拟滚动器:哪个 DOM 元素是可滚动容器
estimateSize: () => 35, // 每个虚拟项(比如一行)的预估高度,单位是像素(px)
})
让我们深入研究更多示例!
原理分析
TanStack Virtual 的核心原理是 虚拟化 (Virtualization),也称为 窗口化 (Windowing)。它不是通过渲染列表中的所有元素,而是 只渲染当前在视口(viewport)中可见的元素及其周围少量元素。这样做可以显著提高长列表的性能,尤其是在处理成千上万个项目时。
核心思想
想象一个拥有 10,000 个项目的列表。如果一次性将所有 10,000 个 div 或 tr 元素渲染到 DOM 中,浏览器会因为处理大量的 DOM 节点而变得非常慢,导致滚动卡顿、内存占用过高。
TanStack Virtual 的做法是:
- 只渲染 “窗口” 内的项目:它计算当前用户可见的区域(即 “窗口” 或 “视口”)内应该显示哪些项目。
- 动态调整位置:它为这些可见项目计算精确的
top或transform(translateY或translateX)CSS 样式,将它们精确定位到在整个长列表中的正确位置。 - 使用一个大的占位符:在可滚动容器内,它会创建一个 一个非常大的内部元素(通常是
div),其高度(或宽度)等于整个虚拟化列表所有项目(包括那些未渲染的)的总高度(或宽度)。这个巨大的占位符让浏览器认为它确实有一个很长的可滚动区域,从而使滚动条能够正常工作。 - 动态测量与估计:为了准确计算总高度和每个可见项目的位置,TanStack Virtual 需要知道每个项目的尺寸。
- 估计尺寸 (
estimateSize):你必须提供一个函数,用来 估计 每个项目的尺寸。这是初始渲染和计算总高度的基础。 - 动态测量 (
measureElement):对于尺寸不固定或不确定的项目,你可以选择在项目渲染后,让 TanStack Virtual 实际测量 它们的尺寸。一旦测量完成,虚拟器会更新其内部计算,调整后续项目的定位。
流程分析
Virtualizer 的工作流程
让我们结合您提供的 API 文档来理解这个过程:
- 初始化
Virtualizer:
count: 你告诉虚拟器总共有多少个项目。getScrollElement: 你告诉虚拟器哪个 DOM 元素是可滚动的容器(视口)。estimateSize: 你提供一个函数,用来估计每个项目的尺寸。这个估计对于计算总高度和初始滚动位置至关重要。
- 计算总尺寸 (
getTotalSize):
Virtualizer根据count和estimateSize计算出整个虚拟化列表的 理论总尺寸(高度或宽度)。这个值被用来设置那个大的内部占位符元素的高度/宽度。
- 监听滚动事件:
Virtualizer会监听getScrollElement返回的 DOM 元素的滚动事件。
- 确定可见项目 (getVirtualItems):
- 当滚动发生时,
Virtualizer会:- 获取当前的滚动偏移量 (
scrollOffset)。 - 获取视口尺寸 (
scrollRect的width/height)。 - 根据这些信息以及每个项目的估计或实际尺寸,精确计算出当前哪些项目应该在视口中显示。
- 它还会加上
overscan选项定义的额外项目,以减少滚动时出现空白区域的可能性。
- 获取当前的滚动偏移量 (
- 渲染可见项目:
- 你通过
virtualizer.getVirtualItems().map(...)遍历这些可见的VirtualItem对象。 - 对于每个
virtualItem:- 你使用
virtualItem.key作为key属性(提高渲染效率)。 - 你使用
virtualItem.index来获取实际的数据。 - 最关键的是,你使用
virtualItem.start来设置项目的 CSStransform样式,如transform: translateY(${virtualItem.start}px)。这会将项目精确地放置在相对于那个大占位符的正确位置。 - 你使用
virtualItem.size来设置项目的尺寸(width或height)。
- 你使用
- 动态测量与调整:
- 如果你设置了
measureElement选项,并且将virtualizer.measureElement绑定到每个渲染出的项目上(例如通过ref回调),那么当这些项目实际渲染到 DOM 中时,Virtualizer会测量它们的 真实尺寸。 - 如果实际尺寸与
estimateSize返回的估计尺寸不同,Virtualizer会 自动调整 其内部的尺寸计算和后续项目的定位,确保滚动条的长度和项目的相对位置保持准确。