使用场景: 在移动端使用下拉加载数据的时候,随着不断从服务端拉取数据,数据列表内容越来越多,导致创建了很多的节点,这个时候vue的diff就需要对比很多次,造成性能消耗和内存占用。
虚拟列表的实现分两种:
列表项高度固定
列表项高度动态
高度固定实现过程:
容器高度(可视区高度):viewport
列表项高度:itemSize
可视区域展示列表个数: viewCount
列表长度:phantomHeight (itemSize * 列表个数)
第一个元素顶部的距离:startOffset
开始元素的下标: startIndex
结尾元素的下标: endIndex
关键点就是确定需要渲染的列表个数,然后根据滚动时动态改变startIndex、endIndex、startOffset值,然后对列表项数据进行过滤切割,获取需要渲染的数据列表。
v3+ts实现代码:
<template>
{
{startIndex}}
{
{endIndex}}
<div class="viewport" @scroll="scrollListBox" ref="viewport">
<div class="v-list" :style="{ height: phantomHeight + 'px', transform: `translateY(${startOffset}px)`}">
<div class="v-list-item" v-for="item in filterData" :key="item.num">
{
{item.num}}
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import { PropType, ref, computed } from 'vue'
interface listItem {
index: number,
num: number
}
const props = defineProps({
data: {
type: Array as PropType<listItem[]>,
default: () => {}
}
})
const viewport = ref<HTMLElement>()
let startIndex = ref<number>(0)
let itemSize = 100
let viewCount = 10
let phantomHeight = itemSize * props.data.length
let startOffset = computed(() => {
return startIndex.value * itemSize
})
let endIndex = computed(() => {
return startIndex.value + viewCount
})
let filterData = computed(() => {
return props.data.slice(startIndex.value, endIndex.value)
})
const scrollListBox = () => {
startIndex.value = Math.floor((viewport.value?.scrollTop || 0) / itemSize)
}
</script>
<style scoped>
.viewport {
width: 500px;
height: 500px;
overflow: scroll;
}
.v-list-item {
height: 100px;
width: 100%;
background: #fff;
color: #000;
line-height: 100px;
border-bottom: 1px solid #ccc;
}
</style>