list-v
- 虚拟列表只渲染可视区域内的数据节点,因此可以提高渲染性能
- 解决列表数据量太大时,页面会卡顿问题
组件参数:
- data:数据源
- listHeight:控制虚拟列表容器高度
- itemHeight:控制每个列表项的高度
- width:控制虚拟列表容器宽度
- labelKey:数据项的label字段的键名
- showSearch:控制是否展示搜索框
封装虚拟列表组件
<template>
<!--
虚拟列表长度items:{{data.length}},
列表高度totalHeight:{{totalHeight}},
实际列表长度visibleItems:{{visibleItems.length}},
开始索引startIndex:{{visibleStartIndex}},
滚动高度scrollTop:{{visibleStartIndex*itemHeight}}
-->
<div class="container" :style="{width:width}">
<slot name="header"></slot>
<el-input type="text" v-model="searchText" placeholder="搜索" clearable v-if="showSearch"/>
<div class="wrapper" @scroll="onScroll" :style="{height:listHeight+'px'}">
<div :style="{height: totalHeight+'px'}">
<div :style="{transform: `translateY(${visibleStartIndex*itemHeight}px)`}">
<div :style="{height: itemHeight+'px'}" v-for="(item,index) in visibleItems" :key="index">
<slot :item="item"></slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import { ref, computed } from 'vue';
interface PropsType {
data: any[],
width?: string,
listHeight?: number,
itemHeight?: number,
labelKey?: string,
showSearch?: boolean
}
const props = withDefaults(defineProps<PropsType>(), {
data: ()=>[],
width: '100%',
listHeight: 300,
itemHeight: 40,
labelKey: 'label',
showSearch: true
});
const searchText = ref('');
let _data = computed(() => props.data.filter(item => item[props.labelKey].includes(searchText.value)));
const _visibleItemCount = Math.ceil(props.listHeight/props.itemHeight)
const visibleStartIndex = ref(0);
const totalHeight = computed(() => _data.value.length * props.itemHeight);
const visibleItems = computed(() => _data.value.slice(visibleStartIndex.value, visibleStartIndex.value + _visibleItemCount));
const onScroll = () => {
const dom = document.querySelector('.wrapper');
if(dom){visibleStartIndex.value = Math.floor(dom.scrollTop / props.itemHeight);}
};
</script>
<style lang="scss" scoped>
.container {
border: 1px solid #eee;
background: #fff;
padding: 15px;
box-shadow: 1px 3px 2px #eee;
border-radius: 3px;
.wrapper {
overflow-y: auto;
border: 1px solid #ccc;
}
}
</style>
使用虚拟列表组件
<VList
:data="rightList"
:listHeight="200"
:itemHeight="30"
>
<template #header>
</template>
<template #default="{item}">
<div class="item">
{{item.label}}
</div>
</template>
</VList>
const data = ref([
{
key: 1,
label: '备选项1',
disabled: false,
},
])
组件参数配置
| 参数 | 说明 | 类型 | 默认值 | 是否必传 |
|---|---|---|---|---|
| data | 数据源 | array | [] | 是 |
| width | 宽度 | string | 100% | 否 |
| listHeight | 列表高度 | number | 300 | 否 |
| itemHeight | 列表项高度 | number | 40 | 否 |
| labelKey | 数据项的label字段的键名 | string | label | 否 |
| showSearch | 是否显示搜索框 | boolean | true | 否 |
插槽
| 插槽名 | 说明 | 参数 |
|---|---|---|
| header | 自定义头部区域 | 无 |
| default | 自定义列表项 | item |
原理
原理:列表只渲染可见区域的DOM,减少DOM数量,提升大数据量列表的渲染性能。
- 当列表滚动时,动态计算当前可见区域的起始索引,
- 根据可见区域起始索引计算可见的数据项,渲染列表。
- 使用translateY将渲染出的列表移动到可见区域的起始索引处