虚拟列表业务封装思路分享

提出背景

公司组会上提到下拉列表元素过多的话会导致页面卡顿,而懒加载+分页的方案并不能阻止页面dom增多,所以就需要使用虚拟滚动方案。

我翻看掘金和csdn上的文章后发现很多是手写虚拟滚动方案,虽然手写虚拟滚动能够实现高度定制化并做到性能优于第三方库,但是开发效率也是不可忽视的一环。

引用第三方库虽然不利于极致的性能优化但是能大大提高开发效率(特别是这种复杂的功能)。

什么是虚拟滚动该如何解决

【全网最通俗易懂】虚拟列表1-(基础篇):渲染十万条数据不卡顿(附demo和源码)大家好,我是前端大卫。 今天想和大家分 - 掘金

这篇文章讲解的很清楚了

虚拟滚动有什么好处

  • 减少DOM数量可以减少浏览器需要渲染和维持的DOM元素数量,进而内存占用也随之减少,这使得浏览器可以更快地响应用户操作
  • 浏览器的回流reflow和重绘repaint操作通常是需要大量计算的,并且随着DOM元素的增多而变得更加频繁和复杂,通过虚拟滚动个减少需要管理的DOM数量。
  • 更快的首屏渲染时间,超大列表的全量渲染很容易导致首屏渲染时间过长。

解决方案(elementUI、elementPlus)

vue2

虚拟滚动+自动分页

因为公司很多项目是vue2项目,所以这里使用标准vue2写法。

基于vue2的elementUI组件是不支持虚拟滚动的,所以需要我们自己实现。

我这里推荐的第三方库是:vue-virtual-scroll-list,只需要npm i vue-virtual-scroll-list下载即可。

我的测试版本:"element-ui": "^2.15.14" "vue":"^2.6.14" "vue-virtual-scroll-list":"^2.3.5"

首先我们要二次封装一下el-select

/components/virtual-select.vue

这里手写节流函数throttle来控制分页触发频率,及无论用户滚动有多块都只保证规定时间内只触发一次分页。

javascript 复制代码
<template>
  <el-select v-model="value" clearable filterable>
  
    <!-- 
       :data-key="'value'"        指定数据唯一标识字段 
       :data-sources="data"       数据来源 
       :data-component="item"     列表项展示组件 
       :estimate-size="10"        自定义行高 
       :keeps="10"                保持渲染的DOM节点数量 
    -->
    
    <virtual-list
      class="list"
      style="height: 250px; overflow-y: auto"
      :data-key="'value'"
      :data-sources="data"
      :data-component="item"
      :estimate-size="10"
      :keeps="10"
    />
  </el-select>
</template>
<script>
import VirtualList from "vue-virtual-scroll-list";
import ListItem from "./item.vue";

//手写节流函数
function throttle(fn, wait) {
  let lastTime = 0
  return function(...args) {
    const now = Date.now()
    if (now - lastTime >= wait) {
      fn.apply(this, args)
      lastTime = now
    }
  }
}

export default {
  name: "VirtualSelect",
  components: { VirtualList },
  data() {
    return {
      options: [],
      data: [],
      value: "",
      pageNo: 0,
      item: ListItem,
    };
  },
  
  // 组件挂载前获取数据
  beforeMount() {
    this.getList();
  },
  methods: {
    // 模拟获取大数据(初始化时调用)
    getList() {
      const data = [];
      for (let i = 0; i < 25000; i++) {
        data.push({ label: "选择" + i, value: "选择" + i });
      }
      this.allData = data;
      this.data = data;
      this.getPageList();
    },
    // 获取分页数据(用于滚动加载)
    getPageList(pageSize = 10) {
      this.pageNo++;
      const list = this.data.slice(0, pageSize * this.pageNo);
      this.options = list;
    },
    // 修改后的加载方法
    loadMore: throttle(function() {
      this.getPageList();
    }, 200) // 200ms节流间隔
  },
};
</script>

<style>
  *::-webkit-scrollbar {
    display: none;
  }
  * {
    scrollbar-width: none;
  }
  * {
    -ms-overflow-style: none;
  }
</style>

/components/item.vue

virtual-select.vue组件中的最小item元素

react 复制代码
<template>
    <el-option :label="source.label" :value="source.value"></el-option>
</template>
  
  <script>
  export default {
    name: 'ListItem',
    props: {
      source: {
        type: Object,
        default() {
          return {}
        }
      }
    }
  }
  </script>
  
  <style scoped>
  </style>

在需求界面使用

react 复制代码
<template>
  <virtual-select v-model="selectedValue" :data="largeDataArray" />
</template>

<script>
import VirtualSelect from "../src/components/virtual-select.vue"; // 修改导入路径

export default {
  components: {
    VirtualSelect, 
  },
  data() {
    return {
      selectedValue: "",
      // 初始化测试数据
      largeDataArray: Array.from({ length: 25000 }, (_, i) => ({
        label: `选择${i}`,
        value: `选择${i}`,
      })),
    };
  },
};
</script>

<style></style>

成果展示:

可以看到我模拟了25000条数据进行测试,通过下图看出只渲染了10个item,这是由于我在virtual-select.vue组件中设置的:keeps="10" 这个参数是保持渲染的DOM节点数量,这个数量越多,缓冲区的元素数量也就越多,所以这个参数可以多设置一些。


vue3

基于vue3的elementplus已经集成了虚拟列表功能:select-v2

Virtualized Select 虚拟化选择器 | Element Plus

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端