vue3 虚拟列表的组件封装

1,188 阅读1分钟

背景

在项目中可能会碰到后端一次性返回超大量的数据。
假使一共返回了 n 条数据,如果不使用虚拟列表,那么我们要生成 n 个 DOM 节点来渲染页面; 如果使用了虚拟列表,那么我们只需要生成少量的节点(取决于要展示多少条),可以大大减少页面卡顿。


原理

  1. 监听滚动操作,获取当前滚动的高度
  2. 根据滚动的高度获取需要展示的首条数据下标
  3. 根据下标和需要展示多少条,在长列表中截取,并对列表进行偏移

文字有点有点抽象,上图会清晰那么一点 - -

虚拟列表.png


代码

<template>
  <div ref="listWrap" class="virtual-list-wrap" @scroll="scrollListener">
    <div ref="list" class="virtual-list">
      <div
        v-for="item in showList"
        :key="item[itemKey]"
        class="virtual-list-item"
      >
        <slot :item-info="item" />
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, defineProps, onMounted } from 'vue'

const props = defineProps({
  itemKey: { type: String, default: 'id' },
  listData: { type: Array, default: () => [] }, // 列表数据
  itemHeight: { type: Number, default: 0 }, // 单个子项高度
  showNum: { type: Number, default: 0 }, // 需要给用户展示的数量
  startIndex: { type: Number, default: 0 }, // 起始下标
  endIndex: { type: Number, default: 0 } // 结束下标
})

const listWrap = ref(null) // 获取列表视图模型节点
const list = ref(null) // 获取列表节点
const start = ref(props.startIndex)
const end = ref(props.endIndex)

onMounted(() => {
  // 设置列表视图模型的高度
  listWrap.value.style.height = props.itemHeight * props.showNum + 'px'
})

const showList = computed(() => {
  // 获取展示的列表
  return props.listData.slice(start.value, end.value)
})

const scrollListener = () => {
  // 获取滚动高度
  const scrollTop = listWrap.value.scrollTop

  // 开始索引
  start.value = Math.floor(scrollTop / props.itemHeight)
  // 结束索引
  end.value = start.value + props.showNum

  list.value.style.transform = `translateY(${start.value * props.itemHeight}px)`// 对列表项进行偏移
}

</script>

<style lang="scss" scoped>
  .virtual-list {
    &-wrap {
      overflow-y: scroll;
    }
  }
</style>