虚拟滚动结合vue提效的关键是仅对可视区域做响应式追踪并复用vnode:用普通数组存大数据,computed提取可视子集,模板遍历visibleitems;结构静态、key唯一、动态内容限于文本节点;极致场景可用h()手写render函数,配合shallowref减少深层响应开销。

虚拟滚动本身不依赖 Vue 的虚拟 DOM,但和它结合能进一步提效——关键在于让 Vue 只更新“真正需要变化”的 VNode,避免无意义的 diff 和重建。
只对可视区域做响应式追踪
默认情况下,Vue 会对整个列表数据做响应式代理(如 ref(list)),哪怕只滚动一屏,list 的全部元素仍参与依赖收集。这会带来两重开销:响应式系统维护大量 getter/setter;渲染函数中遍历全部数据生成 VNode。
实战建议:
- 把原始大数据存为普通数组(const rawList = Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `item-${i}` }))),不包裹成 ref 或 reactive
- 用 computed 按需提取当前可视范围的子集:const visibleItems = computed(() => rawList.slice(startIndex.value, endIndex.value))
- 模板中只遍历 visibleItems,这样 Vue 只对几百个节点做响应式绑定和 VNode 创建
静态结构 + 动态内容复用 key
每个列表项 DOM 结构高度一致时(如纯文本卡片),可大幅减少 VNode 的创建与 diff 成本。Vue 在 patch 阶段若发现 key 相同、标签名相同、props 大致一致,会直接复用旧 VNode,跳过属性比对和子树重建。
操作要点:
根据 package.json 中的 engines 字段自动检测 Node.js 版本需求,未安装 nvm 时自动安装并切换至对应版本。支持 Windows、macOS、Linux。适用于 Node.js 项目开发、版本切换和环境配置。
- 确保每项有稳定且唯一的 key(推荐用数据本身的 id,而非 index)
- 避免在 v-for 内使用内联对象或函数调用(如 :class="{ active: item.id === selected }" 会强制新建 VNode)
- 把动态内容限制在最内层文本节点,例如:{{ item.name }},而不是包裹多层带条件 class 的 div
用 render 函数绕过模板编译开销(进阶)
对于极致性能场景(如每秒滚动上百行的实时日志流),可舍弃模板语法,改用 h() 手写 render 函数。这样跳过了 Vue 模板编译、AST 解析、指令解析等环节,VNode 构建更轻量、可控性更强。
示例片段:
- 定义 render 函数:return () => h('div', { class: 'scroller', onScroll: handleScroll }, [ h('div', { style: { height: `${paddingTop}px` } }), ...visibleItems.value.map(item => h('div', { key: item.id, class: 'item', style: { top: `${item.offsetTop}px`, position: 'absolute' } }, item.name)), h('div', { style: { height: `${paddingBottom}px` } }) ])
- 所有样式、定位、占位都由 JS 精确控制,VNode 创建完全按需,无冗余属性
配合 shallowRef 减少深层响应式开销
当列表项是复杂对象(含嵌套属性),而你只关心整体替换(不响应内部字段变更),可用 shallowRef 替代 ref 存储 visibleItems。
好处是:
- Vue 不会对每个 item 做 reactive 包装,节省内存和初始化时间
- diff 时只比对数组引用是否变化,不深入对比每个 item 的属性
- 适合日志、消息流等“追加即不可变”的数据场景









