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

提出背景

公司组会上提到下拉列表元素过多的话会导致页面卡顿,而懒加载+分页的方案并不能阻止页面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

相关推荐
招风的黑耳13 分钟前
Web元件库 ElementUI元件库+后台模板页面(支持Axure9、10、11)
前端·elementui·axure
雯0609~14 分钟前
CSS:使用内边距时,解决宽随之改变问题
前端·css
Dolphin_海豚24 分钟前
10 分钟带你入坑 electron
前端·javascript·electron
乐闻x34 分钟前
性能优化:javascript 如何检测并处理页面卡顿
前端·javascript·性能优化
雯0609~39 分钟前
vue3:八、登录界面实现-忘记密码
前端·javascript·vue.js
爱看书的小沐1 小时前
【小沐学Web3D】three.js 加载三维模型(React)
javascript·react.js·vue·webgl·three.js·opengl·web3d
烂蜻蜓1 小时前
深入理解 HTML 中的<div>和元素:构建网页结构与样式的基石
开发语言·前端·css·html·html5
木木黄木木1 小时前
HTML5 Canvas弹跳小球游戏开发实战与技术分析
前端·html·html5
Anlici2 小时前
Axios 是基于 Ajax 还是 Fetch?从源码解析其实现
前端·面试
一个处女座的程序猿O(∩_∩)O2 小时前
Vue 中的 MVVM、MVC 和 MVP 模式深度解析
前端·vue.js·mvc