vue3 + element-plus 封装虚拟列表组件

865 阅读2分钟

list-v

  • 虚拟列表只渲染可视区域内的数据节点,因此可以提高渲染性能
  • 解决列表数据量太大时,页面会卡顿问题

组件参数:

  1. data:数据源
  2. listHeight:控制虚拟列表容器高度
  3. itemHeight:控制每个列表项的高度
  4. width:控制虚拟列表容器宽度
  5. labelKey:数据项的label字段的键名
  6. 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宽度string100%
listHeight列表高度number300
itemHeight列表项高度number40
labelKey数据项的label字段的键名stringlabel
showSearch是否显示搜索框booleantrue

插槽

插槽名说明参数
header自定义头部区域
default自定义列表项item

原理

原理:列表只渲染可见区域的DOM,减少DOM数量,提升大数据量列表的渲染性能。

  1. 当列表滚动时,动态计算当前可见区域的起始索引,
  2. 根据可见区域起始索引计算可见的数据项,渲染列表。
  3. 使用translateY将渲染出的列表移动到可见区域的起始索引处