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虚拟列表实现的资料请关注脚本之家其它相关文章!

相关文章

  • vue中的render函数、h()函数、函数式组件详解

    vue中的render函数、h()函数、函数式组件详解

    在vue中我们使用模板HTML语法来组建页面的,使用render函数我们可以用js语言来构建DOM,这篇文章主要介绍了vue中的render函数、h()函数、函数式组件,需要的朋友可以参考下
    2023-02-02
  • vue3+vite应用中添加sass预处理器问题

    vue3+vite应用中添加sass预处理器问题

    这篇文章主要介绍了vue3+vite应用中添加sass预处理器问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • 基于vue组件实现猜数字游戏

    基于vue组件实现猜数字游戏

    这篇文章主要为大家详细介绍了基于vue组件实现猜数字游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • vue.js前端网页弹框异步行为示例分析

    vue.js前端网页弹框异步行为示例分析

    这篇文章主要为大家介绍了vue.js前端网页弹框异步的行为示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步,早日升职加薪
    2021-11-11
  • vue中的事件绑定举例详解

    vue中的事件绑定举例详解

    这篇文章主要给大家介绍了关于vue中事件绑定的相关资料,事件绑定在Web开发中非常常见,我们经常需要在页面中为某个DOM元素绑定事件,如点击、鼠标移动、键盘敲击等等,需要的朋友可以参考下
    2023-09-09
  • 面试官问你Vue2的响应式原理该如何回答?

    面试官问你Vue2的响应式原理该如何回答?

    可能很多小伙伴之前都了解过 Vue2实现响应式的核心是利用了ES5的Object.defineProperty 但是面对面试官时如果只知道一些模糊的概念。只有深入底层了解响应式的原理,才能在关键时刻对答如流,本文就来和大家详细聊聊,感兴趣的可以收藏一下
    2022-12-12
  • vue前端灵活改变后端地址两种方式

    vue前端灵活改变后端地址两种方式

    最近在学习或工作中遇到,把Vue前端项目打包后,要求可以再次修改请求后端接口的基础地址,下面这篇文章主要给大家介绍了关于vue前端灵活改变后端地址的两种方式,需要的朋友可以参考下
    2024-03-03
  • vue组件命名和props命名代码详解

    vue组件命名和props命名代码详解

    在本篇内容里小编给大家讲的是关于vue组件命名和props命名的相关知识点内容,有兴趣的朋友们可以学习下。
    2019-09-09
  • 一文带你了解Vue3中toRef和toRefs的用法

    一文带你了解Vue3中toRef和toRefs的用法

    Vue3中toRef、toRefs都是与响应式数据相关的,本文主要来给大家讲解一下Vue3中的toRef和toRefs的使用,感兴趣的朋友跟随小编一起看看吧
    2023-01-01
  • VUE2语法中$refs和ref属性的使用方式

    VUE2语法中$refs和ref属性的使用方式

    在Vue中如何使用$refs和ref属性来操作DOM和组件实例,$refs是一个对象,包含由ref属性注册的DOM元素和组件实例,通过$refs可以在Vue中获取DOM节点、组件实例、组件数据和属性等
    2025-10-10

最新评论