Vue3虚拟列表的实现原理与实战指南

 更新时间:2026年03月20日 10:05:41   作者:宁雨桥  
在日常开发中,我们经常会遇到需要展示大量数据的场景,比如展示几千条甚至上万条数据列表,如果直接将所有数据渲染到页面中,会导致严重的性能问题,虚拟列表正是为了解决这一问题而诞生的技术,本文将介绍虚拟列表的核心原理,并基于 Vue3 手写一个完整的虚拟列表组件

前言

在日常开发中,我们经常会遇到需要展示大量数据的场景,比如展示几千条甚至上万条数据列表。如果直接将所有数据渲染到页面中,会导致严重的性能问题,页面会变得卡顿甚至崩溃。虚拟列表(Virtual List)正是为了解决这一问题而诞生的技术。

本文将介绍虚拟列表的核心原理,并基于 Vue3 手写一个完整的虚拟列表组件。

一、什么是虚拟列表

虚拟列表是一种按需渲染的技术,它只渲染当前可视区域内的列表项,而不是一次性渲染所有数据。想象一下,你有一本1000页的书,但你在任何时候只能看到其中的一页。虚拟列表的工作方式就和翻书一样——只展示当前"看得到"的内容。

虚拟列表的核心优势

优势说明
性能提升只渲染可见区域的数据,DOM 节点数量大幅减少
内存优化避免创建大量 DOM 节点,降低内存占用
流畅体验滚动更加流畅,不会因为数据量大而卡顿
快速首屏首屏渲染时间大大缩短

二、虚拟列表的实现原理

虚拟列表的核心思想其实很简单,可以用以下几个步骤来概括:

  1. 计算可视区域:根据滚动位置和容器高度,计算出当前可见的数据范围
  2. 按需渲染:只渲染计算出的可见数据,而不是全部数据
  3. 占位模拟:通过 padding 或 transform 模拟完整的滚动高度,让滚动条看起来正常

下面这张图清晰地展示了虚拟列表的工作原理:

┌─────────────────────────────┐
│  已渲染区域(可见)           │
│  ┌─────────────────────┐     │
│  │ Item 3              │     │
│  │ Item 4              │     │
│  │ Item 5              │     │ ← 当前可见区域
│  │ Item 6              │     │
│  │ Item 7              │     │
│  └─────────────────────┘     │
├─────────────────────────────┤
│  未渲染区域(不可见)         │
│  ... Item 8-100              │
└─────────────────────────────┘

三、代码实现

子组件:MyList.vue

<template>
    <div style="overflow-y:auto ;border:3px solid blue;position: relative;" :style="{ height:props.size * 10 + 'px'}" @scroll="handSrool">
        <ul :style="{ height:(props.size+1) * props.testData.length + 'px', transform: `translateY(${offsetY}px)` }" >
            <li v-for="(item, index) in list" class="list-item" :style="{ height:props.size + 'px', position: 'absolute', top: (index * props.size) + 'px', width: '100%' }">
                {{ item }}
            </li>
        </ul>
    </div>
</template>
<script setup>
import { onMounted, ref, computed } from 'vue';
// 接受父节点参数
const props = defineProps({
    testData: Array,
    size: {
        type: Number,
        default: '6'
    }
})
const startIndex = ref(0)
const endIndex = computed(() => startIndex.value + 10)
// 计算偏移量,让列表项跟随滚动
const offsetY = computed(() => startIndex.value * props.size)
const list = computed(() => props.testData.slice(startIndex.value, endIndex.value))
// 监听滚动
const handSrool = (e) => {
    // 获取滚动距离
    const scrollTop = e.target.scrollTop
    // 根据滚动距离计算起始索引
    startIndex.value = Math.floor(scrollTop / props.size)
}
onMounted(() => {
    console.log(props.size)
    console.log(endIndex.value)
})
</script>
<style>
.list-item {
    font-size: 16px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 1px solid rgb(146, 144, 144);
}
</style>

父组件:使用示例

<script setup>
import MyList from './components/MyList.vue';
const data = []
// 模拟数据
for (let i = 0; i < 100; i++) {
  data.push({
    id: i,
    name: `name${i}`,
    age: i,
    sex: i % 2 === 0 ? '男' : '女',
    address: `address${i}`,
    date: new Date(),
  })
}
</script>
<template>
  <div class="table_continue">
     <MyList :testData="data" :size="60"/>
  </div>
</template>
<style scoped>
.table_continue {
  padding: 20px;
}
</style>

四、核心实现解析

1. 计算可见区域的起始索引

const handSrool = (e) => {
    const scrollTop = e.target.scrollTop
    startIndex.value = Math.floor(scrollTop / props.size)
}

这是虚拟列表最核心的逻辑。当用户滚动时,我们通过 scrollTop 获取滚动距离,然后除以单个列表项的高度,就能计算出当前应该从第几条数据开始渲染。

为什么要这样做?

假设每个列表项高度是 60px,滚动位置是 300px,那么 300 / 60 = 5,说明用户已经滚过了5个列表项,所以我们应该从第5条数据开始渲染。

2. 计算可见数据

const list = computed(() => props.testData.slice(startIndex.value, endIndex.value))

使用 slice 方法截取数组中的一部分,只渲染从 startIndex 开始的 10 条数据。这样无论原数组有多大,DOM 中最多只有 10 个列表项元素。

3. 使用 transform 模拟滚动

const offsetY = computed(() => startIndex.value * props.size)

这是实现无缝滚动的关键。虽然我们只渲染了 10 条数据,但通过 transform: translateY() 将整个列表向下偏移,偏移量等于被"跳过"的数据的总高度。这样用户看起来就像在滚动整个列表一样。

4. 占位容器的高度

:style="{ height:(props.size+1) * props.testData.length + 'px' }"

外层容器的高度等于单个列表项高度乘以总数据条数,这就是让滚动条看起来正常的"秘密"。浏览器会根据这个高度生成相应长度的滚动条,用户拖动滚动条时就会触发 scroll 事件。

五、性能优化建议

虽然上面的实现已经能够正常工作,但在生产环境中,你可能还需要考虑以下优化点:

1. 预渲染更多数据

可以在可见区域的前后各多渲染几条数据,避免快速滚动时出现空白:

const BUFFER = 3
const endIndex = computed(() => startIndex.value + 10 + BUFFER)
const list = computed(() => {
    const start = Math.max(0, startIndex.value - BUFFER)
    return props.testData.slice(start, endIndex.value)
})

2. 使用防抖处理滚动事件

如果数据量非常大,可以使用防抖来减少计算频率:

import { debounce } from 'lodash-es'

const handSrool = debounce((e) => {
    const scrollTop = e.target.scrollTop
    startIndex.value = Math.floor(scrollTop / props.size)
}, 16)

3. 处理列表项高度不一致的情况

在实际应用中,列表项的高度可能是动态的。这时可以使用测量行高的方案,或者将所有列表项设为固定高度。

4. 使用 CSS will-change 优化动画

.list-item {
    will-change: transform;
}

这告诉浏览器该元素会发生变化,可以提前进行优化。

六、原理图解

让我们通过一张图来理解整个虚拟列表的工作流程:

用户滚动 → 获取 scrollTop → 计算 startIndex → slice 取数据 → translateY 偏移
     │                                              │
     ↓                                              ↓
┌─────────┐    ┌─────────────┐    ┌──────────┐    ┌────────────┐
│ 滚动事件 │ →  │ scrollTop   │ →  │ 300/60=5  │ →  │ 取第5-15条  │
│ 触发    │    │ = 300px     │    │ start=5   │    │ translateY │
└─────────┘    └─────────────┘    └──────────┘    │ = 300px    │
                                                   └────────────┘

七、总结

虚拟列表是前端性能优化中非常重要的技术之一。它的核心思想可以用一句话概括:只渲染可见的内容,用占位符模拟完整的滚动高度

通过本文的实现,我们可以看到一个基础的虚拟列表组件并不复杂,主要涉及到以下几个关键点:

  1. 监听滚动事件:获取用户的滚动位置
  2. 计算可见索引:根据滚动位置计算应该显示的数据范围
  3. 按需渲染:只渲染计算范围内的数据
  4. 模拟滚动:通过 transform 或 padding 模拟完整的滚动高度

掌握了这些核心原理后,你就可以在此基础上进行各种定制化开发,比如支持动态高度的列表项、添加加载更多功能、实现分组虚拟列表等。

以上就是Vue3虚拟列表的实现原理与实战指南的详细内容,更多关于Vue3虚拟列表实现的资料请关注脚本之家其它相关文章!

相关文章

  • 基于vue2实现上拉加载功能

    基于vue2实现上拉加载功能

    这篇文章主要为大家详细介绍了基于vue2实现上拉加载功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • vue router返回到指定的路由的场景分析

    vue router返回到指定的路由的场景分析

    这篇文章主要介绍了vue router返回到指定的路由的场景分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • vue中的mixins混入使用方法

    vue中的mixins混入使用方法

    这篇文章主要介绍了vue中的mixins混入使用方法,混入又分全局混入混入局部混入,下文对两者都有相关介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-04-04
  • Vue中Vue-Baidu-Map基本使用方法实例

    Vue中Vue-Baidu-Map基本使用方法实例

    最近有一个项目需要用到地图来展示位置并进行数据交互,用vue-baidu-map实现出来,下面这篇文章主要给大家介绍了关于Vue中Vue-Baidu-Map基本使用的相关资料,需要的朋友可以参考下
    2023-03-03
  • vue3中如何使用mqtt数据传输

    vue3中如何使用mqtt数据传输

    这篇文章主要为大家详细介绍了vue3中如何使用mqtt数据传输,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-11-11
  • vue 实现锚点功能操作

    vue 实现锚点功能操作

    这篇文章主要介绍了vue 实现锚点功能操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 在Vue3和TypeScript中大文件分片上传的实现与优化

    在Vue3和TypeScript中大文件分片上传的实现与优化

    本文介绍在 Vue 3 和 TypeScript 环境下大文件分片上传的实现与优化,包括项目前后端技术栈,前端的文件切片、并发上传、计算 Hash、断点续传和用户体验优化,后端的文件接收存储、切片合并、异常处理与日志记录,还提及遇到的问题及解决方案,总结了此方式的优势和重要性
    2025-01-01
  • vue中computed和watch的使用实例代码解析

    vue中computed和watch的使用实例代码解析

    这篇文章主要介绍了vue中computed和watch的综合运用实例,主要需求是点击按钮实现天气的切换效果结合示例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • vue如何处理base64格式文件pdf及图片预览功能

    vue如何处理base64格式文件pdf及图片预览功能

    这篇文章主要给大家介绍了关于vue如何处理base64格式文件pdf及图片预览功能的相关资料,图片的base64编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址,需要的朋友可以参考下
    2024-05-05
  • vue改变对象或数组时的刷新机制的方法总结

    vue改变对象或数组时的刷新机制的方法总结

    这篇文章主要介绍了vue改变对象或数组时的刷新机制的方法总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-04-04

最新评论