mcpField
T
tanstack-vue-virtual-skilld

tanstack-vue-virtual-skilld

github.com/skilld-dev开发184125 次安装3 周前更新
代码仓库

关于 tanstack-vue-virtual-skilld

面向 Vue 开发者的无头列表与网格虚拟化适配器,适用于大型或动态集合。它将 Vue 响应式状态接入 virtual-core 的 Virtualizer,并支持窗口滚动和末端锚定的信息流。

主要功能

  1. 01响应式 useVirtualizer 和 useWindowVirtualizer
  2. 02列表与网格虚拟化
  3. 03动态测量项目尺寸
  4. 04末端锚定信息流与滚动恢复

适用场景

  1. 01减少大型列表渲染的 DOM 项目
  2. 02构建保持末端锚定的聊天信息流
  3. 03导航后恢复滚动位置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/skilld-dev/vue-ecosystem-skills --skill tanstack-vue-virtual-skilld

安装后在 Claude Code 中输入/tanstack-vue-virtual-skilld确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
chat-end-anchoring.md
patterns.md
virtualizer-api.md
vue-adapter.md

详细介绍

@tanstack/vue-virtual

适用于 Vue 的无头列表/网格虚拟化。该适配器很轻量:useVirtualizer 和 useWindowVirtualizer 会构造一个核心 Virtualizer,将其接入 Vue 响应式系统,并以 Ref 形式返回。以下所有选项和方法都来自 @tanstack/virtual-core,适配器会重新导出它(src/index.ts:21)。

版本信息

  • 目标版本:@tanstack/[email protected](package.json:3)。
  • 精确依赖:@tanstack/[email protected](package.json:52)。
  • Peer 依赖:vue ^2.7.0 || ^3.0.0(package.json:59)。
  • 适配器 API 在 3.13.x 版本中保持稳定;3.13.25 到 3.13.39 的版本仅升级了 virtual-core(vue-virtual 更新日志)。
  • 安装:npm install @tanstack/vue-virtual(安装文档)。

快速开始:动态高度行

标准模式参照官方 Vue 动态示例。区块平移会根据第一个项目的 start 定位渲染区块;其中的每个项目按正常流式布局排列。

<script setup lang="ts">
import { computed, onMounted, onUpdated, ref, shallowRef } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'

const parentRef = ref<HTMLElement | null>(null)
const rows = Array.from({ length: 10000 }, (_, i) => `Row ${i}`)

const rowVirtualizer = useVirtualizer({
  count: rows.length,
  getScrollElement: () => parentRef.value,
  estimateSize: () => 55,
  overscan: 5,
})

const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems())
const totalSize = computed(() => rowVirtualizer.value.getTotalSize())

const itemEls = shallowRef<HTMLElement[]>([])
function measureAll() {
  rowVirtualizer.value.measureElement(null) // prune disconnected nodes
  itemEls.value.forEach((el) => el && rowVirtualizer.value.measureElement(el))
}
onMounted(measureAll)
onUpdated(measureAll)
</script>

<template>
  <div ref="parentRef" style="height: 400px; overflow-y: auto; contain: strict">
    <div :style="{ height: `${totalSize}px`, position: 'relative', width: '100%' }">
      <div
        :style="{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          transform: `translateY(${virtualRows[0]?.start ?? 0}px)`,
        }"
      >
        <div
          v-for="virtualRow in virtualRows"
          :key="virtualRow.key"
          :data-index="virtualRow.index"
          ref="itemEls"
        >
          {{ rows[virtualRow.index] }}
        </div>
      </div>
    </div>
  </div>
</template>

滚动容器需要设置固定高度和 overflow: auto。内部的尺寸计算 div 会使用 getTotalSize()。

窗口虚拟化器

useWindowVirtualizer 使用浏览器窗口滚动;它会预先填充 getScrollElement(兼容 SSR)和 initialOffset,因此不要传入这两项(vue-virtual 文档,src/index.ts:104-114)。如果列表上方还有其他内容,请将该偏移量测量后传给 scrollMargin:

const parentOffset = ref(0)
onMounted(() => {
  parentOffset.value = parentRef.value?.offsetTop ?? 0
})

const rowVirtualizer = useWindowVirtualizer(
  computed(() => ({
    count: rows.length,
    estimateSize: () => 45,
    scrollMargin: parentOffset.value,
  })),
)

模式来源:官方窗口示例。

Vue 适配器规则(由 src/index.ts 证实)

  • 返回 Ref<Virtualizer>,而不是原始实例。在脚本中使用 .value;模板会自动解包(src/index.ts:30,69)。
  • 选项接受 MaybeRef。普通对象只会应用一次;若要响应式更新 count、overscan、enabled 等选项,请传入整个选项对象的 ref 或 computed。适配器会监听 () => unref(options),并在值变化时调用 setOptions(src/index.ts:48-65)。
  • 会立即监听 getScrollElement()。如果设置时模板 ref 仍为 null,元素挂载后也会被捕获(src/index.ts:36-46)。
  • onChange 会被包装:适配器会在调用你的回调前,对其内部的 shallowRef 调用 triggerRef,因此 computed(() => rowVirtualizer.value.getVirtualItems()) 会在滚动和调整大小时重新计算(src/index.ts:53-56)。
  • 观察器会通过 onScopeDispose 清理(src/index.ts:67)。
  • 使用 measureElement 测量的项目必须携带索引属性,默认是 data-index(可通过 indexAttribute 配置)。
  • 不要传入 observeElementRect、observeElementOffset 或 scrollToFn;适配器会预先填充这些选项(src/index.ts:83-90)。

详情:Vue 适配器 API。

API 参考

完整的选项表、实例方法和 VirtualItem 结构:Virtualizer API。

useVirtualizer 的必需选项:count、getScrollElement、estimateSize(virtualizer 文档)。

virtual-core 3.14.0 之后的新内容

vue-virtual 3.13.39 使用 core 3.17.11;以下功能随 core 的次要版本推出(virtual-core 更新日志):

  • 3.16.0:新增用于聊天、日志和反向信息流的末尾锚定模式。支持 anchorTo: 'end'、followOnAppend、scrollEndThreshold,以及 scrollToEnd()、isAtEnd()、getDistanceFromEnd()。请参阅聊天和末尾锚定。
  • 3.15.0:新增 takeSnapshot(),可通过 initialMeasurementsCache + initialOffset 往返恢复滚动位置。
  • 3.15.0:默认滚动调整现在会在向后滚动时跳过补偿(修复“向上滚动时项目跳动”);可通过赋值给实例属性 shouldAdjustScrollPositionOnItemSizeChange 覆盖,这不是一个选项。
  • 3.15.0:默认启用 iOS Safari 惯性滚动处理;触摸或回弹期间会延迟写入滚动补偿。
  • 3.17.0:useCachedMeasurements 可在列表隐藏时保留测量结果。
  • 3.15.0 之前仍然适用的选项:lanes 和 laneAssignmentMode(3.14.0)、gap、indexAttribute、initialMeasurementsCache、isRtl。

最佳实践

  • 动态项目应采用较大的估算值:将 estimateSize 设为接近可能出现的最大尺寸,以保持初始位置和滚动条稳定(virtualizer 文档)。
  • 优先使用块级平移(如快速入门所示),而不是逐项绝对定位;平滑滚动时,scrollToIndex 会跳过对远离目标项目的测量,而块级平移可保持已渲染块内部一致(virtualizer 文档)。
  • 每当数据会重新排序、筛选、前置插入或流式传入时,都应设置稳定的 getItemKey(使用行 ID,而非索引);索引键会导致末尾锚定聊天和滚动位置恢复出错(聊天文档)。
  • 如果项目在共享滚动容器内采用绝对定位,应从项目起始位置中减去 scrollMargin(virtualizer 文档)。
  • 使用 gap 选项代替 CSS 外边距,以便 getTotalSize() 将间距计算在内。
  • count 发生变化时会使测量缓存失效,并自动更新 getTotalSize()(在 3.13.13 中修复,更新日志);在 Vue 中,仍应通过 computed 选项传递响应式数据。
  • 除非测量后确认有帮助,否则应关闭 useAnimationFrameWithResizeObserver;它会将测量延迟一帧(virtualizer 文档)。
  • 使用 enabled: false 暂停观察器,而不是卸载组件,以保留测量结果。

有关瀑布流、内边距、粘性标题、滚动位置恢复和隐藏列表的用法示例,请参阅:模式。

从 v2 迁移:useVirtual 已移除;v3 使用 useVirtualizer、count 选项(而非 size)、getScrollElement(而非 parentRef),以及带有 data-index 的 measureElement 引用(vue-virtual 文档)。

参考资料

  • Vue 适配器 API:签名、响应式契约、SSR 行为。
  • Virtualizer API:所有选项、默认值、实例方法、VirtualItem。
  • 模式:瀑布流、滚动边距、粘性范围、滚动位置恢复、隐藏列表。
  • 聊天和末尾锚定:Vue 中使用 anchorTo: 'end' 的聊天信息流。

评论

支持 Markdown