当前位置: 首页 > article >正文

解决 ElSelect 数据量大导致加载速度慢

遇到一个性能相关的问题,使用 Element Plus 的 <ElSelect> 组件在数据量很大时,加载速度变慢。

下面简单分析下原因,并提供了一些解决方法。

1. 问题分析

1、大量 DOM 节点渲染

问题:当数据量非常大时,每一个选项都会生成一个 DOM 节点。在 HTML 中,每一个 <option> 元素都需要单独渲染,导致页面需要处理大量 DOM 元素的加载和渲染,影响页面性能。

影响:浏览器在渲染和操作大量 DOM 时效率会降低,导致组件初始化和操作(如滚动、过滤)变慢。

2、Vue 响应式系统的性能瓶颈

问题:Vue 的响应式系统会追踪每个数据项的状态变化。当 <ElSelect> 中的数据量过大时,Vue 的响应式系统需要为每一个 Option 建立响应式追踪,增加内存和计算的开销,尤其是在更新数据、滚动或筛选时,这种情况会更加明显。

影响:响应式追踪在数据项非常多的情况下可能导致浏览器出现卡顿,甚至出现页面响应不及时的情况。

3、事件监听和计算

问题:当 <ElSelect> 中的数据项很多时,每次选择、过滤或输入,都会触发事件监听器和计算操作。如果数据项非常多,这些操作会变得频繁且耗时,增加组件的负担。

影响:页面响应速度降低,用户在操作组件时会感觉到明显的卡顿。

4、过多的无意义的渲染

问题:在默认实现中,<ElSelect> 会一次性渲染所有数据项,不管用户是否在当前视口中看到这些数据。即便用户只滚动一小部分,整个组件仍然会处理所有数据项,导致加载速度慢。

影响:浏览器资源被过度消耗,渲染效率降低,页面加载时间延长。

2. 解决方案

1、使用虚拟滚动

方法:借助虚拟滚动技术(如 Element Plus 的 ElVirtualizedSelect 组件),只渲染当前视口中可见的部分数据。虚拟滚动技术通过动态加载和卸载数据项来减少页面上的 DOM 节点数量。

这种方法能显著减少 DOM 渲染的节点数量和内存占用,提升渲染速度和用户体验。

🌰

<template>
  <!-- 使用虚拟滚动的选择框 -->
  <el-select-v2
    v-model="selectedValue"
    :options="options"
    placeholder="请选择"
    style="width: 200px"
  />
</template>

<script>
import { ref } from 'vue';

export default {
  setup() {
    // 创建 10,000 条模拟数据
    const options = ref(
      Array.from({ length: 10000 }, (_, index) => ({
        value: index,
        label: `选项 ${index + 1}`
      }))
    );
    const selectedValue = ref(null);
    return {
      options,
      selectedValue
    };
  }
};
</script>

效果:显著减少 DOM 中节点数量,提升了渲染性能。对于大数据场景,只有可见选项会被加载和渲染,大大降低了内存和渲染开销。

文档:https://element-plus.org/zh-CN/component/select-v2.html

对比:普通的 <ElSelect> 组件,在最开始渲染全部的 Option 元素。

<template>
  <!-- 使用普通的选择框 -->
  <el-select v-model="value" placeholder="Select" style="width: 240px">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value"
    />
  </el-select>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    // 创建 100 条模拟数据,防止页面卡住
    const options = ref(
      Array.from({ length: 100 }, (_, index) => ({
        value: index,
        label: `选项 ${index + 1}`
      }))
    )
    const selectedValue = ref(null)
    return {
      options,
      selectedValue
    }
  }
}
</script>

而 <el-select-v2> 组件只渲染展示的一部分,显而易见的提升了渲染性能。

2、分页加载或懒加载

方法:将数据进行分页或分批次加载。比如,可以设置一个加载阈值,先加载一部分数据项,用户向下滚动到一定程度再加载下一部分数据项。

避免一次性加载大量数据,减少页面初始化时的加载压力。

🌰

<template>
  <el-select
    v-model="selectedValue"
    placeholder="请选择"
    filterable
    @visible-change="handleVisibleChange"
  >
    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
</template>
<script>
import { ref, nextTick } from 'vue'
export default {
  setup() {
    const options = ref([])
    const page = ref(1)
    const selectedValue = ref(null)
    // 模拟 API 获取分页数据
    const loadOptions = async () => {
      const newOptions = await fetchOptions(page.value)
      options.value.push(...newOptions)
      page.value++
    }
    // 处理滚动事件
    const handleScroll = (event) => {
      const { scrollTop, clientHeight, scrollHeight } = event.target
      if (scrollTop + clientHeight >= scrollHeight - 10) {
        loadOptions()
      }
    }
    // 监听下拉框的可见性变化
    const handleVisibleChange = async () => {
      await nextTick()
      const dropdown = document.querySelector('.el-select-dropdown .el-scrollbar__wrap')
      if (dropdown) {
        dropdown.addEventListener('scroll', handleScroll)
      }
    }
    // 初始加载
    loadOptions()
    return {
      options,
      selectedValue,
      handleVisibleChange
    }
  }
}
// 模拟 API 调用,获取分页数据
async function fetchOptions(page) {
  return Array.from({ length: 10 }, (_, index) => ({
    value: (page - 1) * 10 + index,
    label: `选项 ${(page - 1) * 10 + index + 1}`
  }))
}
</script>

效果:初次加载仅渲染一部分数据,滚动到列表底部时加载更多。通过分页,可以避免一次性加载全部数据,减少页面初始化的负担。

展示:

以此类推,直到数据加载完成后结束。

3、减少不必要的响应式追踪

方法:将不需要响应式的数据项转换为非响应式对象或深度克隆数据。Vue 3 提供了 shallowRef 和 shallowReactive,可用来减少不必要的响应式开销。

效果:降低 Vue 响应式系统的性能开销,提升加载和操作的流畅度。

🌰

<template>
  <el-select v-model="selectedValue" placeholder="请选择">
    <el-option
      v-for="item in nonReactiveOptions"
      :key="item.value"
      :label="item.label"
      :value="item.value"
    />
  </el-select>
</template>

<script>
import { shallowRef, ref } from 'vue';
export default {
  setup() {
    // 使用 shallowRef 包装不需要响应式的数据
    const nonReactiveOptions = shallowRef(
      Array.from({ length: 1000 }, (_, index) => ({
        value: index,
        label: `选项 ${index + 1}`
      }))
    );
    const selectedValue = ref(null);
    return {
      nonReactiveOptions,
      selectedValue
    };
  }
};
</script>

使用 shallowRef 后,Vue 不会深度监听 nonReactiveOptions 的变化,仅在整个对象被替换时触发重新渲染,这样减少 Vue 对数据的追踪和性能开销。

展示:一次性加载完,但不会跟踪内部变化。

4、减少过度的事件监听

方法:对用户输入和操作添加防抖或节流处理,避免频繁地触发数据项的更新和过滤。比如使用 lodash.debounce 限制输入框触发的过滤频率。

🌰

<template>
  <el-select v-model="selectedValue" filterable @input="onInput" placeholder="请选择">
    <el-option
      v-for="item in filteredOptions"
      :key="item.value"
      :label="item.label"
      :value="item.value"
    />
  </el-select>
</template>

<script>
import { ref, computed } from 'vue';
import debounce from 'lodash/debounce';

export default {
  setup() {
    const options = ref(
      Array.from({ length: 1000 }, (_, index) => ({
        value: index,
        label: `选项 ${index + 1}`
      }))
    );
    const searchQuery = ref('');
    const selectedValue = ref(null);
    // 使用防抖处理输入事件
    const onInput = debounce((value) => {
      searchQuery.value = value;
    }, 300);
    const filteredOptions = computed(() =>
      options.value.filter((item) =>
        item.label.includes(searchQuery.value)
      )
    );
    return {
      filteredOptions,
      selectedValue,
      onInput
    };
  }
};
</script>

效果:只有在输入停止 300 毫秒后,才会触发过滤逻辑,从而避免了输入框内容频繁更新导致的高计算开销。这个方法适用于需要实时过滤的场景。

展示:

总结:

当 Element Plus 的 <ElSelect> 组件加载大量数据时,主要是 DOM 渲染、Vue 响应式追踪和事件计算等导致性能下降。通过使用虚拟滚动、分页加载、减少响应式追踪以及事件防抖等方法,可以显著优化加载性能,使组件在大数据量下也能流畅运行。


http://www.kler.cn/a/392098.html

相关文章:

  • Spark:不能创建Managed表,External表已存在...
  • 算法——二分查找(leetcode704)
  • jenkins提交gitee后自动部署
  • const限定符-C语言中指针的“可变与不可变”法则
  • 基于promtail+loki+grafana搭建日志系统
  • 本地编译ChatNio的问题解决
  • OpenGL【C++】台灯
  • 【AI换脸整合包及教程】深入了解Rope:一款强大的AI换脸工具及其技术原理
  • JavaScript 观察者设计模式
  • Scala的List(可变)
  • 微搭低代码入门02条件语句
  • 【SpringBoot】黑马大事件笔记-day3
  • 用 Python 从零开始创建神经网络(二):第一个神经元的进阶
  • 停车共享小程序ssm+论文源码调试讲解
  • 实现linux定时备份数据至群晖NAS
  • python爬取newbing每日壁纸
  • JDBC事务管理、四大特征(ACID)、事务提交与回滚、MySQL事务管理
  • C语言串讲-2之指针和结构体
  • 2024 ECCV | DualDn: 通过可微ISP进行双域去噪
  • ubuntu20.04 解决Pycharm没有写入权限,无法通过检查更新更新的问题
  • k8s中基于overlay网络和underlay网络的网络插件分别有哪些
  • ima.copilot-腾讯智能工作台
  • react 中 FC 模块作用
  • int溢出值(c基础)
  • next中服务端组件共享接口数据
  • 基于yolov8、yolov5的番茄成熟度检测识别系统(含UI界面、训练好的模型、Python代码、数据集)