antd&vue 大数据场景下的 select 组件

2,028 阅读1分钟

写到前面

antd相信大家都不陌生,在众多开源组件库有着举足轻重的地位;Select 选择器是组件库中常用的组件之一,如何在大数据同样丝滑的使用 select 便成为一个有意思的事情!

方法一:分批加载数据

分页加载数据即是分页加载列表数据,每次多加载一页的数据,依次递增;此方式用 antd 提供的api popupScroll计算列表滑动到底部后多渲染一页的数据;代码如下:

<template>
  <div id="app">
    <a-select
      mode="tags"
      style="width: 100%"
      placeholder="Tags Mode"
      @change="handleChange"
      @popupScroll="handleScroll"
    >
      <a-select-option v-for="i in list" :key="(i + 9).toString(36) + i">
        {{ i }}
      </a-select-option>
    </a-select>
  </div>
</template>

<script>
// import HelloWorld from './components/HelloWorld.vue'
const list = Array.from(Array(10000), (c, i) => i + 1);
const pageSize = 20;

export default {
  name: 'App',
  components: {
    // HelloWorld
  },
  data() {
    return {
      // list: [1, 2, 3, 4, 5, 6, 7, 8, 9],
      pageNum: 1,
    }
  },
  computed: {
    list() {
      return list.slice(0, this.pageNum * pageSize)
    }
  },
  methods: {
    handleChange(value) {
      console.log(`selected ${value}`);
    },
    handleScroll(e) {
      // e.persist();
      const { target } = e;
      // scrollHeight:代表包括当前不可见部分的元素的高度
      // scrollTop:代表当有滚动条时滚动条向下滚动的距离,也就是元素顶部被遮住的高度
      // clientHeight:包括padding但不包括border、水平滚动条、margin的元素的高度
      const rmHeight = target.scrollHeight - target.scrollTop;
      const clHeight = target.clientHeight;
      // 当下拉框失焦的时候,也就是不下拉的时候
      if (rmHeight === 0 && clHeight === 0) {
        console.log('stopScroll');
      } else {
        // 滚动到底部
        if (rmHeight < clHeight + 5) {
          // dispatchState({ type: 'scroll' });
          this.pageNum += 1
        }
      }
    }
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

此方法比较简单,缺点也比较明显

  • 数据超过500条左右时虽然每次只多加载一页的数据但会出现渲染较慢的体感,目测数据量大了vue的dom-diff也会消耗部分时间
  • 取消焦点后重新focus表单,列表卡顿依然比较明显

方法二:自定义下拉列表(虚拟列表)

问题原因归根结底是渲染较大数据的列表(长列表),既然是长列表自然就想到了虚拟列表的优化方案;使用虚拟列表渲染select下拉列表岂不美哉。。。

只需要三步即可完成:

  • 使用vue-virtual-scroller渲染下拉列表,借用 select 提供的 dropdownRender api自定义下拉菜单
  • 自定义单条itemclick事件
  • 自定义 select 选中 代码如下:
<template>
  <div id="app">
    <a-select
      :value="value"
      style="width: 100%"
      placeholder="Tags Mode"
    >
      <template v-if="list.length">
        <div slot="dropdownRender" class="custom-dropdown">
          <RecycleScroller
            class="scroller"
            :items="list"
            key-field="id"
            :item-size="32"
          >
            <template slot-scope="{ item }">
              <li
                class="dropdown-item"
                @click="dropdownItemHandler(item)"
              >
                {{ item }}
              </li>
            </template>
          </RecycleScroller>
        </div>
      </template>
    </a-select>
  </div>
</template>

<script>
const list = Array.from(Array(10000), (c, i) => i + 1);
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
import { RecycleScroller } from 'vue-virtual-scroller';

export default {
  name: 'App',
  components: {
    RecycleScroller,
  },
  data() {
    return {
      value: undefined,
      list: list,
    }
  },
  methods: {
    dropdownItemHandler(value) {
      console.log(`selected ${value}`);
      this.value = value;
    },
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.custom-dropdown {
  height: 250px;
}
.custom-dropdown .scroller {
  height: 100%;
  overflow-y: scroll;
}
.custom-dropdown .dropdown-item {
  padding: 5px 12px;
  color: rgba(0, 0, 0, 0.65);
}
.custom-dropdown .dropdown-item:hover {
  background-color: #e6f7ff;
}
</style>

此方法实现的效果相对比较完美,不再受数据量大小的限制;虽然需要自定义部分select已有的功能😛😛😛

写到最后

这里就不多废话,相信看完上边内容的同学对于两种方法的优劣自有见解,本文仅是解决问题的记录;代码传送门