Lzh on GitHub

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 的做法是:

  1. 只渲染 “窗口” 内的项目:它计算当前用户可见的区域(即 “窗口” 或 “视口”)内应该显示哪些项目。
  2. 动态调整位置:它为这些可见项目计算精确的 toptransformtranslateYtranslateX)CSS 样式,将它们精确定位到在整个长列表中的正确位置。
  3. 使用一个大的占位符:在可滚动容器内,它会创建一个 一个非常大的内部元素(通常是 div),其高度(或宽度)等于整个虚拟化列表所有项目(包括那些未渲染的)的总高度(或宽度)。这个巨大的占位符让浏览器认为它确实有一个很长的可滚动区域,从而使滚动条能够正常工作。
  4. 动态测量与估计:为了准确计算总高度和每个可见项目的位置,TanStack Virtual 需要知道每个项目的尺寸。
  • 估计尺寸 (estimateSize):你必须提供一个函数,用来 估计 每个项目的尺寸。这是初始渲染和计算总高度的基础。
  • 动态测量 (measureElement):对于尺寸不固定或不确定的项目,你可以选择在项目渲染后,让 TanStack Virtual 实际测量 它们的尺寸。一旦测量完成,虚拟器会更新其内部计算,调整后续项目的定位。

流程分析

Virtualizer 的工作流程

让我们结合您提供的 API 文档来理解这个过程:

  1. 初始化 Virtualizer:
  • count: 你告诉虚拟器总共有多少个项目。
  • getScrollElement: 你告诉虚拟器哪个 DOM 元素是可滚动的容器(视口)。
  • estimateSize: 你提供一个函数,用来估计每个项目的尺寸。这个估计对于计算总高度和初始滚动位置至关重要。
  1. 计算总尺寸 (getTotalSize):
  • Virtualizer 根据 countestimateSize 计算出整个虚拟化列表的 理论总尺寸(高度或宽度)。这个值被用来设置那个大的内部占位符元素的高度/宽度。
  1. 监听滚动事件:
  • Virtualizer 会监听 getScrollElement 返回的 DOM 元素的滚动事件。
  1. 确定可见项目 (getVirtualItems):
  • 当滚动发生时,Virtualizer 会:
    • 获取当前的滚动偏移量 (scrollOffset)。
    • 获取视口尺寸 (scrollRectwidth/height)。
    • 根据这些信息以及每个项目的估计或实际尺寸,精确计算出当前哪些项目应该在视口中显示。
    • 它还会加上 overscan 选项定义的额外项目,以减少滚动时出现空白区域的可能性。
  1. 渲染可见项目:
  • 你通过 virtualizer.getVirtualItems().map(...) 遍历这些可见的 VirtualItem 对象。
  • 对于每个 virtualItem
    • 你使用 virtualItem.key 作为 key 属性(提高渲染效率)。
    • 你使用 virtualItem.index 来获取实际的数据。
    • 最关键的是,你使用 virtualItem.start 来设置项目的 CSS transform 样式,如 transform: translateY(${virtualItem.start}px)。这会将项目精确地放置在相对于那个大占位符的正确位置。
    • 你使用 virtualItem.size 来设置项目的尺寸(widthheight)。
  1. 动态测量与调整:
  • 如果你设置了 measureElement 选项,并且将 virtualizer.measureElement 绑定到每个渲染出的项目上(例如通过 ref 回调),那么当这些项目实际渲染到 DOM 中时,Virtualizer 会测量它们的 真实尺寸
  • 如果实际尺寸与 estimateSize 返回的估计尺寸不同,Virtualizer自动调整 其内部的尺寸计算和后续项目的定位,确保滚动条的长度和项目的相对位置保持准确。