> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# 下拉框大数据量优化

## 业务场景

近期着手开发基于 ElementUI 的后台管理系统，偶然间发现「**el-select**」下拉选择时候遇到一个问题，当渲染下拉选项的「**options**」的数据量过多时「本项目中的 **数据条目已过万**」，就会出现下拉选择器卡顿的情况，尤其是在模糊匹配过滤的情况下，显得十分的卡顿。初始化选择器的时候，也会点击无反应，有时候需要点击多次才可出现 **「dialog」** 弹窗（本次下拉筛选在弹窗中实现）。
翻阅多篇博客笔记之后，最终找到一个可以解决问题的方案，现将此次优化方案记录成为笔记，以便于日后遇到类似问题的时候便于查阅。

## 图示效果

![image.png](data:image/png;base64,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)
:::info 注
基于 el-select 的下拉筛选，通过自定义事件来实现模糊搜索匹配。
:::

一共两种方案：

1. 获取所有数据，通过输入的 **关键字** 自己对获取的数据进行过滤处理；
2. 通过输入的 **关键字** 来动态请求后台接口，通过接口返回的数据来动态渲染下拉选项；

## 代码实现

- 🌰Vue 组件实例

```html title="代码示例"
<template>
    <div class="app">
        <el-dialog
            title="标题"
            :visible.sync="relatedOpen"
            :append-to-body="true"
            width="500px"
        >
            <el-row :gutter="16">
                <el-col :span="20">
                    <el-select
                        v-model="value"
                        filterable
                        clearable
                        style="width:100%"
                        placeholder="请选择"
                        :loading="searchLoad"
                        :filter-method="filterMethod"
                        v-el-select-loadmore="loadMore(rangeNumber)"
                        @visible-change="visibleChange"
                    >
                        <el-option
                            v-for="item in options.slice(0, rangeNumber)"
                            :key="item.key"
                            :label="item.value"
                            :value="item.key"
                        ></el-option>
                    </el-select>
                </el-col>
                <el-col :span="4">
                    <el-button type="primary" @click="submit">确定</el-button>
                </el-col>
            </el-row>
        </el-dialog>
    </div>
</template>
```

- 🚗「**v-el-select-loadmore**」为自定义指令封装的数据加载指令，是为了解决和优化 elementUI 下拉选择器加载数据过多出现卡顿问题的。
- 🚴「**filter-method**」是下拉选择器的一个自定义属性，可以监听输入的变量，从而依据变量来实现数据的动态获取；

```javascript title="自定义指令"
  // 自定义指令
  directives: {
    "el-select-loadmore": (el, binding) => {
      // 获取Element UI定义好的scroll元素
      const SELECTWRAP_ROM = el.querySelector(".el-select-dropdown .el-select-dropdown__wrap");
      if (SELECTWRAP_ROM) {
		// 添加scroll事件
        SELECTWRAP_ROM.addEventListener("scroll", function() {
			// 判断滚动
          const condition = this.scrollHeight - this.scrollTop <= this.clientHeight;
          condition && binding.value();
        });
      }
    }
  }
```

- 相应的数据函数

```javascript title="数据函数"
export default {
    data() {
        return {
            relatedOpen: false,
            options: [] /* 选择下拉框的值 */,
            courseList: [],
            rangeNumber: 10,
            searchLoad: false /* 下拉框的loading状态 */,
            value: "",
            timer: null,
        };
    },
    created() {
        this.getOptions();
    },
    methods: {
        // 按需加载
        loadMore(n) {
            return () => (this.rangeNumber += 5);
        },
        // 过滤课件
        filterMethod(query) {
            if (this.timer != null) clearTimeout(this.timer);
            !this.searchLoad && (this.searchLoad = true);
            this.timer = setTimeout(() => {
                this.options = !!query
                    ? this.courseList.filter((el) =>
                          el.value.toLowerCase().includes(query.toLowerCase())
                      )
                    : this.courseList;
                clearTimeout(this.timer);
                this.searchLoad = false;
                this.rangeNumber = 10;
                this.timer = null;
            }, 500);
        },
        // 监听select下拉框的显示和隐藏
        visibleChange(flag) {
            // 显示时初始化列表
            flag && this.filterMethod("");
            // 初始化默认值
            this.rangeNumber = 10;
        },
        // 获取选项
        async getOptions() {
            await searchCourseware().then((res) => {
                let list = res.data || [];
                // 默认展示的数据
                this.options = list;
                // 原始数据
                this.courseList = list;
            });
        },
    },
};
```

:::danger 注意事项

- 定时器作用是防止输入筛选的关键字太过频繁，从而造成数据响应不及时；因为本次是一次性获取了全部的数据，所以这里只是用做渲染加载数据；
- 选择器的事件函数主要是用来初始化“获取焦点”和“失去焦点”时处理默认展示数据用的，若是获取的网络请求，此处需要做“函数截流”处理。目的是减少接口请求次数。

:::

## 总结：

换了新的工作环境，现开始着手做后台管理系统，或多或少会遇到各种各样的问题。一如即往，会在笔记中记录开发中遇到的问题。好记性不如烂笔头，希望现在埋下种子，等到来年秋天的时候会收获 🍒 果实。⛽️「加油 😯」
