做 Vue3 开发的小伙伴应该都遇到过这个问题:当列表数据达到几百甚至几千条时,页面轻则加载缓慢,重则滚动卡顿,用户体验直接拉胯,甚至还会被产品追着改需求。
其实长列表的性能问题,核心无非就是DOM 元素过多、渲染压力过大、无用操作频繁这几个原因。今天就给大家分享 7 个可直接落地的 Vue3 长列表优化技巧,从核心方案到细节优化,全覆盖,看完就能抄作业,让你的长列表滚动丝滑到极致!
🚀 核心方案:虚拟列表,从根源减少 DOM
这是优化长列表最有效的方法,没有之一!普通列表会一次性渲染所有数据,哪怕有 10000 条,DOM 元素就有 10000 个,浏览器根本扛不住;而虚拟列表的核心思路是只渲染当前视口中的内容,不在视口的内容直接不渲染,DOM 数量能从几千上万骤减到几十,性能直接拉满。
Vue3 生态有现成的虚拟列表组件vue-virtual-scroller,开箱即用,步骤超简单:
1. 安装依赖
javascript
npm install vue-virtual-scroller
2. 组件中引入并使用
javascript
<template>
<RecycleScroller
class="scroller"
:items="longList"
:item-size="50"
key-field="id"
>
<template v-slot="{ item }">
<div class="list-item">{{ item.content }}</div>
</template>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
// 模拟10000条长列表数据
const longList = Array.from({ length: 10000 }, (_, i) => ({
id: i,
content: `Item ${i}`
}))
</script>
<style scoped>
.scroller {
height: 500px;
overflow-y: auto;
}
.list-item {
height: 50px;
line-height: 50px;
border-bottom: 1px solid #eee;
}
</style>
哪怕列表有 10000 条数据,这个组件会自动计算可视区域,DOM 数量始终保持在几十左右,滚动起来毫无压力。
📸 按需加载:懒加载,减轻初始渲染压力
如果长列表里包含大量图片或复杂子组件,哪怕用了虚拟列表,初始加载时一次性处理所有资源也会让页面卡顿。这时候懒加载就派上用场了 ------ 元素进入视口时才加载内容,完美避开初始加载的性能瓶颈。
Vue3 中用vue3-lazyload插件就能轻松实现图片懒加载,还能自定义加载中和加载失败的占位图:
1. 安装插件
javascript
npm install vue3-lazyload
2. 全局引入(main.js)
javascript
import { createApp } from 'vue'
import App from './App.vue'
import VueLazyload from 'vue3-lazyload'
createApp(App)
.use(VueLazyload, {
loading: '/loading.gif', // 加载中占位图
error: '/error.png' // 加载失败占位图
})
.mount('#app')
3. 组件中使用
javascript
<template>
<div v-for="item in longList" :key="item.id" class="list-item">
<img v-lazy="item.imageUrl" alt="列表图片">
<p>{{ item.content }}</p>
</div>
</template>
这样图片只有滚动到可视区域时才会发起请求加载,页面初始加载速度直接翻倍。
🧹 精简响应式:markRaw,拒绝无用的性能开销
Vue3 的响应式系统虽然强大,但不是所有数据都需要做响应式处理!比如长列表中的静态数据、只展示不修改的数据,强行做响应式会让 Vue 额外进行依赖追踪,增加性能开销。
这时候用markRaw标记数据,就能让 Vue 跳过响应式处理,直接减少底层的性能消耗:
javascript
import { markRaw } from 'vue'
// 标记后,数据不再是响应式,Vue不会追踪其变化
const staticList = markRaw(Array.from({ length: 10000 }, (_, i) => ({
id: i,
content: `Static Item ${i}`
})))
小贴士:此方法仅适用于纯静态数据,若数据需要后续修改,切勿使用!
🔧 组件优化:让列表项 "轻装上阵"
如果列表项组件写得过于复杂,哪怕数据量不大,也会让渲染变慢。针对列表项组件,从这 3 个点优化,立竿见影:
1. 拆分组件
把复杂的列表项拆分成多个小组件,比如将 "图片区、文字区、操作区" 分开,减少单个组件的渲染压力,也更易维护。
2. v-once 标记静态内容
用v-once指令标记列表项中的静态内容,让 Vue 只渲染一次,后续不再更新:
javascript
<template>
<div class="list-item">
<!-- 静态内容,只渲染一次 -->
<p v-once>{{ item.staticContent }}</p>
<!-- 动态内容,正常响应式 -->
<p>{{ item.dynamicContent }}</p>
</div>
</template>
3. 事件委托替代单独绑定
如果列表项有大量点击、hover 等事件,不要给每个项单独绑定事件,用事件委托把事件绑定到父容器上,减少事件监听的数量。
📄 备选方案:分页加载,简单粗暴易实现
如果项目场景不适合用虚拟列表(比如需要展示完整列表结构),或者懒加载的优化效果不够,分页加载就是最稳妥的备选方案 ------ 把长列表分成多页,每次只加载并渲染一页数据,从根源控制 DOM 数量。
实现一个 "加载更多" 式的分页加载,代码超简单:
javascript
<template>
<div>
<div v-for="item in listData" :key="item.id" class="list-item">
{{ item.content }}
</div>
<button @click="loadNextPage" v-if="hasMore">加载更多</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const page = ref(1) // 当前页码
const pageSize = ref(20) // 每页条数
const total = ref(1000) // 总数据量
const listData = ref([]) // 已加载数据
const hasMore = ref(true) // 是否还有更多数据
// 加载数据
const loadData = async () => {
// 模拟接口请求
const res = await fetch(`/api/list?page=${page.value}&pageSize=${pageSize.value}`)
const data = await res.json()
listData.value.push(...data.list)
page.value++
// 判断是否加载完毕
if (listData.value.length >= total.value) {
hasMore.value = false
}
}
// 加载下一页
const loadNextPage = () => {
loadData()
}
// 初始加载第一页
loadData()
</script>
分页加载的优势是实现简单、兼容性好,适合大部分中小型项目的长列表场景。
🎨 CSS 优化:避开复杂选择器,减少浏览器渲染耗时
很多开发者会忽略 CSS 对长列表的影响,其实复杂的 CSS 选择器会让浏览器花费更多时间匹配元素,尤其是长列表有大量重复元素时,这种耗时会被无限放大。
优化原则:用简单的类选择器,避免嵌套过深、避免多层后代选择器。
css
/* ❌ 不好的写法:嵌套过深,浏览器匹配耗时 */
.list-container .list-item .item-content p {
color: #333;
}
/* ✅ 好的写法:单独类选择器,匹配速度快 */
.item-content-text {
color: #333;
}
简单的 CSS 选择器能让浏览器的渲染引擎更快工作,间接提升列表的滚动流畅度。
⏱️ 滚动优化:requestAnimationFrame,避免频繁触发
如果长列表需要监听滚动事件(比如实现无限滚动、滚动加载),直接监听会导致事件高频触发,主线程被阻塞,进而引发滚动卡顿。
解决方法:用requestAnimationFrame包裹滚动事件的处理函数,让函数在浏览器下一次重绘前执行,避免频繁触发,减少性能损耗:
javascript
const handleScroll = () => {
requestAnimationFrame(() => {
// 获取滚动相关数据
const scrollTop = document.documentElement.scrollTop
const clientHeight = document.documentElement.clientHeight
const scrollHeight = document.documentElement.scrollHeight
// 滚动到底部前100px时,加载下一页
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadNextPage()
}
})
}
// 监听滚动事件
window.addEventListener('scroll', handleScroll)
小技巧 :还可以给滚动事件添加passive: true,进一步提升滚动性能:
javascript
window.addEventListener('scroll', handleScroll, { passive: true })
✨ 最后总结:优化核心就这 3 点
其实 Vue3 长列表的优化,不用追求花里胡哨的方案,抓住核心思路,根据项目场景组合使用上述技巧即可:
-
减少 DOM 元素数量:虚拟列表、分页加载是核心手段;
-
减轻渲染压力:懒加载、拆分组件、v-once 标记静态内容;
-
避免不必要的操作:markRaw 精简响应式、简化 CSS 选择器、requestAnimationFrame 优化滚动。