前端大数据量渲染优化的两种方案

198 阅读2分钟

虚拟列表

使用vue-virtual-scroller,本次的使用场景是用于element-plus中的select下拉选择组件。

封装组件如下:(此处用到的是recycle-scroller)

<template>
  <div style="width: 100%">
    <el-select
      style="width: 100%"
      v-model="selectedIds"
      clearable
      filterable
      :filter-method="dataFilter"
      :placeholder="placeholderText"
      v-loading="isLoading"
      multiple
      collapse-tags
      :max-collapse-tags="props.elSelectShowNum"
      :disabled="props.isDisabledSelect"
      @visible-change="handleVisibleChange"
      @change="handleSelectChange"
    >
      <recycle-scroller
        v-show="visible"
        :items="filteredOptions"
        :item-size="34"
        :buffer="50"
        :prerender="10"
        style="height: 200px"
        key-field="id">
        <template #default="{ item }">
          <el-option
          :label="item.name"
          :value="item.id"
          :key="item.id"
          :disabled="disabledChannelIds.includes(item.id) || tableData.some((row) => row.id === item.id)"></el-option>
        </template>
      </recycle-scroller>
    </el-select>
  </div>
</template>

<script setup>

const props = defineProps({
  // 下拉选项数据
  optionData: {
    type: Array,
    default: () => []
  },
  // 是否显示加载中
  isLoading: {
    type: Boolean,
    default: false
  },
  // 需要显示的 Tag 的最大数量
  elSelectShowNum: {
    type: Number,
    default: 1
  },
  // 是否需要禁用select下拉框(禁用后无法点击并出现下拉框),默认不禁用
  isDisabledSelect: {
    type: Boolean,
    default: false
  },
  // 所有的禁用项
  disabledChannelIds: {
    type: Array,
    default: () => []
  },
  placeholderText: {
    type: String,
    default: '请选择'
  },
  // 表格数据(根据表格数据判断是否禁用某些选项)
  tableData: {
    type: Array,
    default: () => []
  }
})

let selectedIds = ref([])
const filteredOptions = ref([]);
const visible = ref(false);

// 为filteredOptions赋初始值,否则一开始就无数据
watchEffect(() => {
  filteredOptions.value = props.optionData;
});

const dataFilter = (val) => {
  if (!val) {
    // 没有输入搜索字时,即显示全部下拉选项
    filteredOptions.value = props.optionData;
    return;
  }
  // 有输入搜索字时,过滤下拉选项
  filteredOptions.value = props.optionData.filter((option) => option.name.toLowerCase().includes(val.toLowerCase()));
};

const handleVisibleChange = (boolean) => {
  // 控制 RecycleScroller 的可见性
  visible.value = boolean;
};

const emit = defineEmits(["selectChange"]);
const handleSelectChange = (val) => {
  // 选中值发生变化时,通知父组件
  selectedIds.value = val;
  emit("selectChange", val);
};

// 将resetData暴露出去,供父组件调用
// 通过defineExpose

const resetData = () => {
  // 重置下拉选项
  selectedIds.value = [];
  filteredOptions.value = props.optionData;
};

defineExpose({resetData})

</script>
<style scoped>
/* 整个滚动条 */
::-webkit-scrollbar {
  width: 3px;
  height: 3px;
}

/* 滚动条有滑块的轨道部分 */
::-webkit-scrollbar-track-piece {
  background-color: transparent;
  border-radius: 5px;
}

/* 滚动条滑块(竖向:vertical 横向:horizontal) */
::-webkit-scrollbar-thumb {
  cursor: pointer;
  background-color:#999999;
  border-radius: 5px;
}

/* 滚动条滑块hover */
::-webkit-scrollbar-thumb:hover {
  background-color: #999999;
}

/* 同时有垂直和水平滚动条时交汇的部分 */
::-webkit-scrollbar-corner {
  display: block;    /* 修复交汇时出现的白块 */
}
</style>

滚动加载

使用element-plus的Infinite Scroll无限滚动,核心思想是:数据全量加载,页面分段渲染。

第一次加载前100条,滚动到页面底部后,从全量数据中拿到第100~200这100条,push到用于渲染的renderData中,此时renderData应有200条数据。以此类推。

代码如下:

<template>
    <ul class="monitor-list" v-infinite-scroll="loadMore" style="overflow: auto" >
    <li
        v-for="item in monitorDataRender"
        :key="item.id"
        @click="toClickMonitor(item)"
        :class="{'li-active': currentMonitor.id === item.id,'li-out-line': item.status == '0'}"
       >
      {{ item.name }}
    </li>
  </ul>
</template>
<script setup>
// 从接口获取到的全量的数据
const monitorList = ref([]);
// 用于渲染的数据
const monitorDataRender = ref([])
const getMonitorList = () => {
  // 从接口拿到数据
  DeviceChannelList(params).then((res) => {
    monitorList.value = res.data;
    monitorDataRender.value = res.data.slice(0, 100)
  })
}
const loadMore = () => {
  if (monitorDataRender.value.length < monitorList.value.length) {
    monitorDataRender.value.push(...monitorList.value.slice(monitorDataRender.value.length, monitorDataRender.value.length + 100))
  }
}
</script>