深入解析 VueUse useVirtualList:在 Vue 3 中实现高性能虚拟滚动列表

📅 发布时间:2026/9/11 7:16:03
深入解析 VueUse useVirtualList:在 Vue 3 中实现高性能虚拟滚动列表
深入解析 VueUse useVirtualList在 Vue 3 中实现高性能虚拟滚动列表【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi导读useVirtualList是 VueUse 中专门用于构建**虚拟列表Virtual List / Virtual Scroller**的组合式函数它只渲染可视区域内所需的少量 DOM 节点同时用一个包装层wrapper模拟容器的完整高度从而让 9 万、10 万条数据的长列表也能流畅滚动。本文以 useVirtualList.md 为骨架结合仓库实际依赖情况VueUse 已被本仓库的apps/stage-pocket与packages/stage-ui等项目通过vueuse/core引入完整覆盖其 API、配置参数、模板用法、类型声明、边界问题与替代方案读完即可在 Vue 3 / Nuxt 3 项目中落地一个可复制的虚拟滚动组件。前置条件需要 Vue 3或以上/ Nuxt 3或以上项目并安装vueuse/core。本仓库的 apps/stage-pocket/package.json 与 packages/stage-ui/package.json 中均通过vueuse/core: catalog:声明了该依赖说明 VueUse 已是本仓库 Vue 技术栈的标准基础设施。一、虚拟列表的核心原理container wrapper 双层结构在没有虚拟滚动时渲染 10 万条div意味着创建 10 万个真实 DOM 节点浏览器布局与绘制开销会直接拖垮 UI 线程。虚拟列表的思路是只渲染可视区域加上少量缓冲内的条目即最小必要 DOM 节点数用一个高度或宽度等于完整数据总量的 wrapper 元素撑起滚动条的真实长度监听容器container的滚动事件动态计算当前应渲染的起止下标并通过translateY/marginTop把可视条目推到正确的位置。useVirtualList的返回值恰好就是这套结构的三块拼图containerProps要绑到外层滚动容器的属性ref、onScroll、stylewrapperProps要绑到中间模拟层撑起全高的元素的属性计算后的宽高与偏移list当前应被渲染的条目数组UseVirtualListItemT[]每个元素形如{ data, index }。模板结构固定为三层嵌套外层容器绑定containerProps并设置固定可视高度中间层绑定wrapperProps最内层用v-for遍历list渲染真实条目。二、基础用法简单列表最简单的调用方式传入数据数组与itemHeight配置返回list、containerProps、wrapperProps。import { useVirtualList } from vueuse/core const { list, containerProps, wrapperProps } useVirtualList( Array.from(Array.from({ length: 99999 }).keys()), { // 必须与每个条目行的实际高度保持一致 itemHeight: 22, }, )在模板中三层结构如下template div v-bindcontainerProps styleheight: 300px div v-bindwrapperProps div v-foritem in list :keyitem.index styleheight: 22px Row: {{ item.data }} /div /div /div /template这里styleheight: 300px是容器的可视窗口高度决定一屏内渲染多少条而每个条目的height: 22px必须与itemHeight: 22严格一致详见下文配置参数一节。三、配置参数详解参数类型说明itemHeightnumber每个条目的高度像素用于确保 wrapper 元素的总高度计算正确itemWidthnumber每个条目的宽度像素用于横向虚拟列表确保 wrapper 总宽度计算正确overscannumber可视区域外预渲染的条目数缓冲快速滚动时可避免条目之间出现空白默认值为5⚠️ 关键同步约束itemHeight/itemWidth必须与实际渲染的每一行/列的真实尺寸保持一致。官方文档明确指出如果滚动到底部时看到多余空白或抖动jitter请检查itemHeight/itemWidth是否与行的实际高度一致。这是使用useVirtualList最常见的坑——由于 wrapper 的总高度是按itemHeight × 数据总数计算的一旦真实行高更大滚动条长度就会虚高产生空白与错位。从 useVirtualList.md 的类型声明可以看到实际类型比表格更灵活type UseVirtualListItemSize number | ((index: number) number)也就是说itemHeight/itemWidth既可以传固定像素值也可以传按索引返回尺寸的函数用于不定高/瀑布流场景。接口层面的默认值注释为default 0即不传时会按 0 计算总尺寸overscan的默认值为5。注意源码层面的默认0通常只适用于配合函数式尺寸的进阶用法对固定行高场景务必显式传入真实尺寸否则列表无法正确显示。四、响应式列表与 computed / useToggle 组合useVirtualList的第一个参数类型为MaybeRefreadonly T[]因此既可以传普通数组也可以传ref或computed。这意味着数据源变化时虚拟列表会自动重算并保持响应式。下面是一个切换偶数/奇数过滤的经典示例用useToggle控制过滤条件用computed派生数据再把filteredList交给useVirtualListimport { useToggle, useVirtualList } from vueuse/core import { computed } from vue const [isEven, toggle] useToggle() const allItems Array.from(Array.from({ length: 99999 }).keys()) const filteredList computed(() allItems.filter(i isEven.value ? i % 2 0 : i % 2 1)) const { list, containerProps, wrapperProps } useVirtualList( filteredList, { itemHeight: 22, }, )template pShowing {{ isEven ? even : odd }} items/p button clicktoggle Toggle Even/Odd /button div v-bindcontainerProps styleheight: 300px div v-bindwrapperProps div v-foritem in list :keyitem.index styleheight: 22px Row: {{ item.data }} /div /div /div /template每行v-for中的item是UseVirtualListItemT包含两个字段item.index条目在原始完整数组中的下标不是当前可视窗口内的相对下标适合作为:key以及做精确跳转定位item.data条目的原始数据本身。五、横向虚拟列表虚拟滚动不限于纵向。当数据量大且需要横向滚动如时间轴、歌曲轨道、缩略图长廊时把配置参数从itemHeight换成itemWidth即可import { useVirtualList } from vueuse/core const allItems Array.from(Array.from({ length: 99999 }).keys()) const { list, containerProps, wrapperProps } useVirtualList( allItems, { itemWidth: 200, }, )template div v-bindcontainerProps styleheight: 300px div v-bindwrapperProps div v-foritem in list :keyitem.index stylewidth: 200px Row: {{ item.data }} /div /div /div /template注意横向模式下条目由height变为width: 200px与itemWidth: 200保持同步containerProps同样负责滚动监听与样式。从类型声明看横向与纵向分别对应两个选项接口export interface UseHorizontalVirtualListOptions extends UseVirtualListOptionsBase { /** item width, accept a pixel value or a function that returns the width */ itemWidth: UseVirtualListItemSize } export interface UseVerticalVirtualListOptions extends UseVirtualListOptionsBase { /** item height, accept a pixel value or a function that returns the height */ itemHeight: UseVirtualListItemSize } export type UseVirtualListOptions | UseHorizontalVirtualListOptions | UseVerticalVirtualListOptions即二选一传入itemWidth即为横向模式传入itemHeight即为纵向模式。六、组件式用法UseVirtualList 与 scrollTo如果不想手写三层结构VueUse 还提供了组件封装UseVirtualList需按 VueUse 的组件导入方式引入。它接收list、options、height三个 prop并通过**默认插槽default slot**暴露当前条目的index与datatemplate UseVirtualList :listlist :optionsoptions height300px template #defaultprops !-- 在这里拿到当前条目 -- div styleheight: 22px Row {{ props.index }} {{ props.data }} /div /template /UseVirtualList /template其中options即前文配置对象{ itemHeight: 22, overscan: 5 }等height控制容器的可视高度。组件方式把 container / wrapper 的模板细节封装了起来适合列表结构固定、追求声明式写法的场景。组件还暴露了一个定位方法scrollTo(index: number) void可编程滚动到指定下标条目。组合式 API 版本的返回值同样包含该方法见下节类型声明中的scrollTo可用于实现回到顶部定位到第 N 条滚动高亮等功能。七、返回类型与函数签名全解useVirtualList的完整签名与返回类型如下摘自 useVirtualList.mdexport interface UseVirtualListItemT { data: T index: number } export interface UseVirtualListReturnT { list: RefUseVirtualListItemT[] scrollTo: (index: number) void containerProps: { ref: RefHTMLElement | null onScroll: () void style: StyleValue } wrapperProps: ComputedRef{ style: | { width: string height: string marginTop: string } | { width: string height: string marginLeft: string display: string } } } export declare function useVirtualListT any( list: MaybeRefreadonly T[], options: UseVirtualListOptions, ): UseVirtualListReturnT逐项解读list: RefUseVirtualListItemT[]响应式的当前可见条目列表直接供v-for使用scrollTo(index: number): void编程式滚动到指定下标containerProps包含ref绑定真实容器元素、onScroll内部滚动处理绑定后自动触发重算、style容器的尺寸/overflow 等样式整体v-bind到外层容器即可wrapperProps一个ComputedRef其style在纵向模式下为{ width, height, marginTop }高度撑满总长、用marginTop平移可视区横向模式下为{ width, height, marginLeft, display }宽度撑满总宽、用marginLeft平移display用于横向布局。这正是wrapper 模拟容器全高/全宽原理的实现载体——尺寸与位移都由库自动计算无需手写。八、使用建议何时用 useVirtualList何时换更强的库官方文档在开头就给出了明确的选型提示如果你在寻找更多特性请优先考虑使用tanstack/vue-virtual。同时在类型声明注释中也提到追求更多特性时可考虑vue-virtual-scroller。这意味着useVirtualList的定位是轻量、开箱即用固定行高/列宽、简单过滤、普通长列表场景下它 API 简洁、零额外依赖仅依赖vueuse/core足以胜任绝大多数需求当需要动态测量行高、窗口化嵌套、复杂的滚动同步、列表项懒加载/回收复用等高级能力时应迁移到功能更完整的tanstack/vue-virtual等方案。从本仓库的 skill 组织方式看useVirtualList被归类在 .agents/skills/vueuse-functions/SKILL.md 的Component组件类别下其Invocation规则为AUTO——即适用时自动使用是 VueUse 在 Vue/Nuxt 项目中处理长列表渲染的默认推荐手段。这也与本仓库大量使用.vue组件与组合式 API如packages/stage-ui、apps/stage-pocket的技术栈高度契合。九、实战排查清单围绕虚拟列表最容易踩的坑汇总一份排查清单出现多余空白 / 底部抖动→ 核对itemHeight或itemWidth是否与真实行列尺寸一致行内含 padding、border、字体行高差异都会导致偏差。快速滚动时条目之间闪白→ 增大overscan缓冲数量默认 5让可视区之外多预渲染几条。数据源变化后列表不更新→ 确认传给useVirtualList的是ref/computedMaybeRef语义而不是每次渲染都新建的普通数组。需要定位到指定条目→ 使用返回值中的scrollTo(index)或组件版UseVirtualList暴露的scrollTo(index)。行高不定→ 使用UseVirtualListItemSize的函数形式(index) number按需计算若行高需真实 DOM 测量则考虑tanstack/vue-virtual这类带动态测量的方案。容器没有固定高度/宽度→ 外层容器必须显式设置可视尺寸如height: 300px否则无法形成滚动上下文虚拟列表无从生效。结语useVirtualList用container 监听滚动 wrapper 撑起全长 list 只渲染可见项的三层模型把一个通常需要手写大量边界逻辑的虚拟滚动方案压缩成了几十行代码且天然响应式、支持纵向/横向两种模式、支持scrollTo定位。对于本仓库这类大量使用 Vue 3 组合式 API 与 UnoCSS 的应用而言它是在不引入重型第三方虚拟滚动库的前提下处理海量列表渲染的标准答案。若需求超出其能力边界官方也明确给出了tanstack/vue-virtual等升级路径。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考