vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue3虚拟列表实现

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

作者:宁雨桥

在日常开发中,我们经常会遇到需要展示大量数据的场景,比如展示几千条甚至上万条数据列表,如果直接将所有数据渲染到页面中,会导致严重的性能问题,虚拟列表正是为了解决这一问题而诞生的技术,本文将介绍虚拟列表的核心原理,并基于 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虚拟列表实现的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文