关于 tanstack-vue-virtual-skilld
面向 Vue 开发者的无头列表与网格虚拟化适配器,适用于大型或动态集合。它将 Vue 响应式状态接入 virtual-core 的 Virtualizer,并支持窗口滚动和末端锚定的信息流。
主要功能
- 01响应式 useVirtualizer 和 useWindowVirtualizer
- 02列表与网格虚拟化
- 03动态测量项目尺寸
- 04末端锚定信息流与滚动恢复
适用场景
- 01减少大型列表渲染的 DOM 项目
- 02构建保持末端锚定的聊天信息流
- 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.mdpatterns.mdvirtualizer-api.mdvue-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'的聊天信息流。