文章目录
- 前言
- Vue 长列表为什么一定会卡?
- 1. v-for 渲染大量节点的真实代价
- 2. DOM 数量过多,回流重绘是致命的
- 3. 图文混排 + 复杂组件 = 雪上加霜
- 核心解法:虚拟列表(Virtual List)
- 1. 虚拟列表原理一句话版
- 2. vue-virtual-scroller 快速上手 Demo
- 安装
- 基础示例(可直接跑)
- 关键点解析
- key 用不好,虚拟列表也救不了你
- 错误示例
- 正确示例
- keep-alive 和组件卸载策略
- keep-alive 的正确用法
- 离开页面主动清理数据
- IntersectionObserver 做懒加载
- 图片懒加载示例
- 实战结果:10 万级表格渲染
- 总结
前言
在 Vue 项目里,只要一遇到长列表,很多同学第一反应都是:
“我这也没干啥啊,就是 v-for 渲染个列表,怎么就卡成 PPT 了?”
更狠一点的,产品一句话:
“这个页面数据有点多,可能十几万条吧。”
然后你就知道,这一页不优化是肯定过不去了。
这篇文章我们就从为什么卡开始,一步一步讲清楚Vue 长列表性能问题的本质,以及真正能落地的解决方案。
Vue 长列表为什么一定会卡?
先说结论一句话版:
卡,不是 Vue 慢,是 DOM 太多。
1. v-for 渲染大量节点的真实代价
假设你写了这样一段代码:
<div v-for="item in list" :key="item.id"> {{ item.name }} </div>如果list有 10 万条数据,意味着什么?
浏览器里会真实创建10 万个 DOM 节点
每次滚动都会涉及:
- 布局计算(Layout)
- 回流(Reflow)
- 重绘(Repaint)
任意一次父组件更新,都可能触发这些节点的 diff
这不是 Vue 的锅,这是浏览器物理极限的问题。
2. DOM 数量过多,回流重绘是致命的
浏览器渲染流水线大概是:
JS → Style → Layout → Paint → Composite当 DOM 数量过多时:
- Layout 时间暴涨
- Scroll 时频繁触发 repaint
- FPS 掉到 30 以下,人眼就明显感觉卡
这也是为什么你会看到:
- 滚动时页面发虚
- 快速滑动直接“锁死”
- 真机比模拟器还卡
3. 图文混排 + 复杂组件 = 雪上加霜
如果列表项里还有:
- 图片
- 自定义组件
- 动态高度
- hover / 动画
那每一行的渲染成本都会进一步放大。
所以,结论很清楚:
长列表,绝对不能一次性渲染完。
核心解法:虚拟列表(Virtual List)
虚拟列表的核心思想其实很简单:
只渲染“屏幕可见的那一小部分 DOM”,其他的用占位撑高度。
1. 虚拟列表原理一句话版
假设屏幕一次最多只能看到 20 行:
- DOM 中永远只存在 20~30 行
- 滚动时,复用 DOM 节点
- 数据在变,DOM 不新增
这就是虚拟列表。
2. vue-virtual-scroller 快速上手 Demo
这是 Vue 里最成熟、最常用的方案之一。
安装
npminstallvue-virtual-scroller基础示例(可直接跑)
<template> <RecycleScroller :items="list" :item-size="50" key-field="id" > <template #default="{ item }"> <div class="row"> {{ item.text }} </div> </template> </RecycleScroller> </template> <script setup> import { RecycleScroller } from 'vue-virtual-scroller' const list = Array.from({ length: 100000 }).map((_, i) => ({ id: i, text: `第 ${i} 行数据` })) </script>关键点解析
item-size:非常重要,最好是固定高度key-field:告诉组件用哪个字段复用 DOM- 内部使用的是 DOM 复用,不是频繁创建/销毁
实测:10 万条数据,滚动依然 60 FPS。
key 用不好,虚拟列表也救不了你
很多列表性能问题,其实是key 用错了。
错误示例
<div v-for="(item, index) in list" :key="index">问题:
- 数据插入 / 删除时
- index 全部变化
- Vue 会误以为所有节点都变了
正确示例
<div v-for="item in list" :key="item.id">记住一句话:
key 一定要稳定、唯一、和业务语义一致。
keep-alive 和组件卸载策略
在分页列表或 Tab 场景中,经常会遇到:
“切换回来列表还在,但怎么感觉越来越卡?”
keep-alive 的正确用法
<keep-alive :max="2"> <router-view /> </keep-alive>- 控制缓存页面数量
- 避免无限缓存导致内存膨胀
离开页面主动清理数据
onDeactivated(()=>{list.value=[]})对于超大列表,离开页面就应该释放内存,不要心疼。
IntersectionObserver 做懒加载
对于图片、复杂组件,可以再加一层优化。
图片懒加载示例
constobserver=newIntersectionObserver(entries=>{entries.forEach(entry=>{if(entry.isIntersecting){entry.target.src=entry.target.dataset.src observer.unobserve(entry.target)}})})- 图片进入可视区才加载
- 极大降低首屏压力
实战结果:10 万级表格渲染
优化前:
- 首屏白屏 3~5 秒
- 滚动 FPS < 30
优化后(虚拟列表 + 懒加载):
- 首屏 < 500ms
- 滚动稳定 60 FPS
总结
Vue 长列表优化,本质是“控制 DOM 数量”。
只要 DOM 在可控范围内,性能问题基本都能解决。