ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Element-ui el-select 下拉框 filterable 与 clearable 组合实战指南

Element-ui el-select 下拉框 filterable 与 clearable 组合实战指南 用了一段时间 Element-ui 的 el-select你会发现一个很别扭的事实默认的下拉选择器选项一多用户就得在列表里来回滚效率低不说还容易选错。filterable 和 clearable 这两个属性正好是解决这种“选择困难症”的标配组合。一个让你能打字搜索快速定位选项一个让你选中之后还能一键清空重新选择听起来都很简单但真正把它们用明白、用出花来中间有不少细节和坑。这篇文章就围绕这两个属性展开结合我在实际业务里碰到的场景把配置方法、原理逻辑、以及那些文档里不会明说的坑一次性讲清楚。无论你是刚接触 Element-ui 的新手还是已经被用户各种奇葩反馈折磨过的老手这篇内容都能给你一些值得参考的东西。1. 项目背景与场景拆解为什么“既能搜又能清空”是刚需1.1 从用户真实痛点反推组件设计逻辑先说说这个需求是怎么来的。我接手过一个后台管理系统里面有个订单查询页面需要筛选对应的客户名称并快速定位订单数据。最初版本就是普通的下拉框客户列表大概有四五百条。结果上线当天运营同事就跑过来吐槽说这个下拉框根本没法用每次选客户都要滚半天运气不好遇到目标是最后一个手都滚酸了。而且更尴尬的是一旦选错了想换一个还得想办法先把当前值清掉再重新打开下拉。这个场景非常有代表性它碰到的就是典型的长列表选择问题。普通的原生 select 在有大量选项时单位的可视面积有限用户几乎无法快速定位目标同时误选后也没有任何快捷键或按钮可以一键抹掉重来。Element-ui 的 el-select 里filterable 属性就是为解决第一个问题设计的它会让选择器变成一个可输入框用户可以输入关键字进行过滤组件内部会自动匹配包含该文本的选项clearable 则是解决第二个问题的在已选中状态下会在右侧出现一个“清除”的小图标点击即还原为空值。这里的核心逻辑有两个一是“搜索定位”提升选择效率二是“可逆操作”降低错误成本。从交互设计角度讲这两个能力本质上是在降低用户在表单操作中的理解成本和操作成本尤其是当选项列表是不受控的、随时可能增长的数据源时这种设计几乎是必需的。1.2 适合使用的典型业务场景与边界判断在实际项目里不是所有下拉框都适合无脑加这两个属性。我总结了一套自己的判断逻辑基本上可以概括为三个“看”看数据量、看选项稳定性、看操作频率。看数据量选项少于 15 个时个人建议不用加 filterable。因为列表本身很短用户一眼就能扫完加搜索框反而多了一步点击/聚焦动作得不偿失。看选项稳定性如果选项是相对固定的比如性别、证件类型、订单状态这种虽然数据量不大但如果有业务含义干扰比如“已完成”和“已关闭”视觉上容易混淆也可以加一个过滤让用户精准输入确认。看操作频率高频使用的查询表单必须配合 filterable 和 clearable 一起上。前者负责高效定位后者负责快速重置能明显减少连续筛选操作的挫败感。还有一类典型的场景是联动选择比如先选省份、再选城市。这个场景下 clearable 尤其重要因为用户可能想换个省份重新选如果旧选中的城市ID还存着联动数据就乱了给用户一个清晰可见的清空入口比让他去点重置整个表单要安全得多。1.3 这两个属性背后的组件设计目标其实从代码层面看filterable 本质上是把 el-select 从“只读展示 点击展开”变成了一个“具备输入能力”的组合控件。它内部会维护一个 query 字符串用户输入的文本会被用来过滤渲染的 option 列表。而 clearable 则是一个状态机当绑定值 v-model 有值且 hover 到组件上时显示清除图标点击后触发什么触发 input 事件吗不是它触发的是 clear 事件同时把当前值置为 undefined。这里有个很容易忽略的点清空后的值是 undefined 而不是空字符串这就意味着如果你用严格等值判断清空和选空字符串是两码事。后面我在常见问题部分会专门讲这个坑。2. 核心属性配置解析与手把手实现2.1 filterable 的基础用法与“内置过滤”真相先来一个最基础的例子这种写法在很多项目里都能看到template div el-select v-modelselectedValue filterable placeholder请选择客户 stylewidth: 320px el-option v-foritem in customerList :keyitem.id :labelitem.name :valueitem.id / /el-select /div /template script export default { data() { return { selectedValue: null, customerList: [] } }, mounted() { // mock 数据实际项目一般从接口获取 this.customerList Array.from({ length: 500 }, (_, i) ({ id: i 1, name: 测试客户${i 1} })) } } /script代码本身很简单不加任何额外逻辑filterable 就能做到输入文字筛选。但要搞清楚它内部是怎么筛的不然遇到需求“按拼音筛选”或者“同时匹配编号和名称”你就会束手无策。Element-ui 内置的筛选逻辑简单说就是拿你输入的关键字去匹配 option 的 label 文本看是否包含这个关键字。它是逐个 option 用 indexOf 之类的方案判断的是“包含”匹配不是“开头”匹配也不是不区分大小写拼音匹配。所以你输入“测试”能匹配到“测试客户1”但输入“ce”是匹配不到的因为 label 并没有“ce”这个子串。2.2 自定义过滤规则filter-method 的进阶用法如果你的需求不只是按 label 过滤而是需要按对象的其他字段匹配或者做形如“编号 名称”联合搜索内置的包含匹配就不够用了。这时候需要用到 filter-method 属性。filter-method 接收一个函数该函数会在用户输入时被调用参数是当前的输入关键字。你可以在这个函数里自定义过滤逻辑并且把过滤结果赋值给一个新的数据源让 el-select 渲染这个新数据源。举个例子我的客户对象里有 id 和 name 两个字段我希望用户输入 id 也能搜到输入 name 也能搜到甚至输入一个关键字同时匹配 id 和 name 都行el-select v-modelselectedValue filterable :filter-methodcustomFilter placeholder支持编号/名称搜索 stylewidth: 100% el-option v-foritem in filteredList :keyitem.id :label${item.id} - ${item.name} :valueitem.id / /el-selectdata() { return { selectedValue: null, customerList: [], filteredList: [] } }, created() { this.customerList [ { id: 1001, name: 张三 }, { id: 1002, name: 李四 }, { id: 1003, name: 王五 } ] this.filteredList this.customerList }, methods: { customFilter(query) { if (query) { const keyword query.trim().toLowerCase() this.filteredList this.customerList.filter(item { return item.name.toLowerCase().includes(keyword) || String(item.id).includes(keyword) }) } else { // 输入框被清空时恢复完整列表 this.filteredList this.customerList } } }这是我在项目里经常用的一种写法特别是表格数据里带编号的场景用户记住编号直接输入数字比翻列表快得多。要注意的是如果采用自定义 filter-method那么原始的 filterable 内置过滤就被覆盖了一切过滤逻辑都由你自己控制返回的选项列表也是你控制的数据源。还有个容易忽略的细节自定义 filter-method 时一旦数据源被替换成 filteredList下拉框的高亮状态和已选中状态有时会出问题特别是当你把 value 定为对象而非基本类型时更是如此。后面我会专门讲 value 用对象时会遇到的问题。2.3 clearable 的细节、事件与联动处理再说 clearable。加了这个属性后只要当前选中值非空鼠标悬停在选择器上右边就会出现一个圆形带叉的小按钮Element-ui 自带样式点击它就能清空值。一个标准的写法el-select v-modelselectedCityId clearable placeholder请选择城市 clearhandleCityClear el-option v-foritem in cityList :keyitem.id :labelitem.name :valueitem.id / /el-selectclear 事件是在点击清空按钮后触发的。注意这个事件触发的同时v-model 绑定的值会变为 undefined或 null 具体看版本但大多情况是 undefined。如果你需要在这个时机重置联动数据比如清空“区县”的下拉值并把区县列表置空完全可以在这个事件里做handleCityClear() { this.selectedDistrictId null this.districtList [] }还有一个实际开发里很常见的需求默认选中了某个省份用户想清空但清空后某些表单校验规则要求该字段必填此时你要在清空后立刻触发一遍校验可以在 clear 事件里调用对应表单项的 validateField 方法否则用户不点提交你根本发现不了校验状态已经过期了。再补充一个 touch 体验细节clearable 的清除按钮默认只在 hover 状态出现在触屏设备上是拿不到的如果你有移动端适配需求建议直接用 Input 类型的 clearable 替代方案或者必须做触摸兼容时从 Element-plus 方案的某些交互借个思路用自定义 suffix 图标常驻替代清除入口。2.4 可搜索 可清空组合使用的最佳实践把两个属性放一起并不冲突它们是两条独立的功能线filterable 管输入框clearable 管清除按钮。在实际项目中我强烈建议两者同时启用尤其是针对远程数据加载和异步筛选的场景。组合起来还有一个进阶玩法远程搜索 清空后重置搜索结果。el-select v-modelselectedUserId filterable clearable remote :remote-methodremoteSearchUser :loadinguserLoading placeholder输入姓名/工号搜索用户 stylewidth: 100% el-option v-foritem in userOptions :keyitem.userId :labelitem.name item.empNo :valueitem.userId / /el-select在这种组合下clearable 点击清空后remote-method 并不会自动触发如果不做任何处理下拉列表会保留上一次搜索的结果。正确的做法是在 clear 事件里再次调用远程搜索方法并传入空字符串让后端返回默认列表async handleUserClear() { this.userOptions [] this.userLoading true try { const res await fetchUserList({ keyword: }) this.userOptions res.data } finally { this.userLoading false } }这个细节很容易被忽略。我在开发协作平台时就是因为没做 clear 后的数据刷新导致用户清空后打开下拉看到的还是上一次搜索的无关联数据被产品找了好几次。3. 实操演示从普通下拉改造成“可搜索可清空”的完整过程3.1 场景设定与初始状态我们模拟一个任务管理后台的“任务负责人选择器”。需求如下负责人数据从接口动态获取总量 1000且会继续增长用户要求输入姓名关键字直接筛选也支持按邮箱搜索选择后如果想要换人必须能一键清除不能靠手动删 DOM 的方式清除后需要恢复默认选项列表而非保留搜索过滤后的列表。初始状态下的代码问题很多没有 filter-method、没有 clearable、数据列表直接渲染 1000 个 option导致打开下拉卡顿。3.2 改造第一步加上 filterable 并观察变化先只加 filterable其余不动。结果输入框可以被聚焦并输入文字但内置过滤只能匹配 label 文本如果 label 是“张三”用户输入“zhangsan”或“zhangxx.com”都无法匹配体验依旧不达预期。这个阶段得出的结论是filterable 只是让 “搜索” 这个交互出现了要真正好用还得配合自定义过滤把用户所有可能的查询维度都加进去。3.3 改造第二步实现多字段自定义过滤我给每个 option 的 label 设置为“姓名工号 / 邮箱”的格式然后在 filter-method 里同时校验姓名、工号、邮箱methods: { filterUserList(query) { if (!query) { this.filteredUserList this.userList return } const keyword query.trim().toLowerCase() this.filteredUserList this.userList.filter(user { return user.name.toLowerCase().includes(keyword) || user.empNo.toLowerCase().includes(keyword) || user.email.toLowerCase().includes(keyword) }) } }实测效果好了不少。用户输入“zhang”或者“01”都能搜出对应人员搜索定位基本满足需求。但这又暴露了新问题如果每次都拿全量 1000 条数据在前端过滤数据量一大浏览器渲染 option 列表的耗时还是会有感知。数据超过两三千时我可以明显感觉到输入关键字后页面卡顿这就是下一步优化的动机。3.4 改造第三步引入远程搜索并加 clearable考虑到未来数据量只会继续增加我决定改用远程搜索每次只请求用户输入关键字匹配的前 50 条el-select v-modelform.ownerId filterable clearable remote reserve-keyword :remote-methodremoteSearchUser :loadinguserLoading placeholder输入姓名/工号/邮箱搜索 stylewidth: 100% el-option v-foritem in userOptions :keyitem.userId :labelformatUserLabel(item) :valueitem.userId / /el-selectremote 模式下remote-method 会在用户输入时被调用我们在方法里调接口然后更新 userOptions。remoteSearchUser(query) { if (query ! ) { this.userLoading true fetchSearchUser(query).then(res { this.userOptions res.records }).finally(() { this.userLoading false }) } else { this.userOptions [] } }clearable 在这里的意义非常大远程搜索状态下如果不加 clearable用户选完之后想重新搜索得先把当前值删掉才能输入新关键字而浏览器原生行为下文本框里显示的又是选中 label操作起来非常别扭。加了 clearable 后一个点击就回到初始状态重新聚焦输入即可。完整的改造过程走完后从体验上说是质的提升不再卡顿、可搜索、可回退、远程数据实时加载。这套组合方案在后来的多个后台表单里我基本都直接套用。4. 常见问题与排查技巧实录4.1 不知道为什么 filterable 输入后筛不出结果这个现象很普遍明明给 el-select 加了 filterable可一输入文字列表直接没了或者过滤后的结果不是预期。排查思路先看数据源是否正常。如果你用了异步数据的场景却忘了在拿到数据后重新赋值给 el-select 渲染的 option 列表那么 filterable 会基于旧的空列表做过滤结果自然啥也搜不到。检查点确认 v-for 循环的列表在数据到达时已经更新。其次检查 option 的 label 是否为字符串。如果 label 是数字 0在判断包含时会出现奇怪的问题尤其在旧版本 Element-ui 中数字 0 会被当成空值处理。建议在传值之前用 String() 预处理一遍。还有很常见的一点用了自定义 filter-method 之后里面的 filter 逻辑写错了比如 search 的参数没传或者直接用 去比较对象导致永远匹配不上。这种时候在方法里打一个 console.log(query, this.filteredList)看值到底是什么问题基本十分钟内定位。4.2 清空按钮不出现或点击没反应clearable 不生效十有八九是当前值“看起来有值实际上不是受控值”。比如你在 data 里声明了 value 为 null但选中的 option 的 value 却是 undefined这种情况下组件内部的判断逻辑可能认为“没有值”所以不会渲染清除按钮。还有一种情况是 el-option 的 value 被定义成了对象但在回显时新对象与原始选中的对象不是同一个引用组件判断 value 是否存在的逻辑就会异常导致清除按钮状态不对。这类问题最有效的排查方法是直接在页面上打印一下当前 v-model 绑定的值看它到底是什么类型、什么内容。点击清除按钮后没反应常见原因是你监听了 clear 事件但事件里写了阻止冒泡stopPropagation把正常的清除逻辑也挡住了。还有可能是样式层被其他元素覆盖清除按钮虽然看着点击了但实际没触发到这种检查一下 z-index 层级即可。4.3 清空之后值变成了 undefined后端提交日期报错这是我踩过最实在的坑表单里有一个数字类型的字段通过 el-select 选择加 clearable 后用户清空了我直接把表单对象传给后端后端收到 undefined 之后解析 JSON 失败接口报 500。根本原因el-select 清空时v-model 的值被设置成 undefined。如果你有默认值需求比如清空后希望设置为 null或者希望设置为空字符串“”不能只靠 clearable 自身完成必须在 clear 事件里手动处理handleFieldClear() { this.form.field null // 或 }更稳妥的方案是在提交前统一对表单字段做一次空值规整处理把所有 undefined 替换成 null再传给后端。不要指望组件帮你把数据清洗做完。4.4 使用 filter-method 自定义过滤后下拉框已选回显不匹配当你用自定义 filter-method 动态替换 option 列表数据源变成了 filteredList而 el-select 的显示逻辑需要通过 option 的 value 找到对应的 label。如果此时 filteredList 里刚好不包含你选中的那条数据比如搜索后数据被过滤掉了组件在渲染“当前选中项”时找不到对应 label就会出现回显空白或直接显示 value 值。解决方案有几个在自定义过滤时把已选中的那条 option 保留在列表中永远不要 filter 掉或者用单独的计算属性存选中项 label然后在 el-select 上通过 placeholder 或自定义内容来展示 label再或者远程搜索场景下在接口返回结果的前端 merge 一层逻辑如果返回列表不包含当前选中项就把选中项追加到列表头部。我惯用第三种方案既能保证回显又不影响搜索精度还能顺带解决用户搜索后选项错位的问题。4.5 搜索时输入中文输入法组件的“筛选”状态不同步中文输入有一个独特的问题输入法组词过程中会触发 input 事件但此时你输入的拼音根本不是你想筛选的内容。比如你输入“zhang”但输入法还在组词el-select 内置过滤已经把拼音当作关键字把列表筛得乱七八糟。遇到这种情况解决方案是在 filter-method 里做一个 300ms 的防抖并配合 composition 事件判断是否为中文输入过程中触发的let timer null let isComposing false methods: { customFilter(query) { clearTimeout(timer) timer setTimeout(() { // 这里过滤数据 this.doFilter(query) }, isComposing ? 300 : 0) }, handleCompositionStart() { isComposing true }, handleCompositionEnd() { isComposing false } }虽然这种场景比较少见但真遇到了会非常影响中文用户的体验特别是在搜索框同时承担着“快速切换”职能时每一次错误过滤都意味着多几次点击。提前处理能省去很多售后。4.6 Element-ui 表格里的行收起操作与 select 的联动小坑最近后台系统里还碰到一个跟表格行收起相关的交互问题表格中每一行展示一个状态下拉选择器用于修改该行数据的状态要求在修改后能整体收起该行。这里直接用 el-select 的 change 事件触发外层表格行的收起函数看起来没问题但实际操作时会发现收起动画太慢用户连点多个下拉状态数据错乱。排查后发现根本原因其实是 el-select 的 change 事件在 close 阶段才触发跟表格行的折叠动画不同步。如果要实现“选择即收起”的效果更好的做法是监听 select 的 visible-change在面板关闭前确认当前值已经提交到数据源然后 setTimeout 折叠该行或者干脆改为按钮确认后再折叠。这个点和 filterable、clearable 本身无关但却是真实业务中组合使用时容易踩坑的地方一并拿出来分享。5. 实操心得与避坑速查表最后给出一张我平时自己用的速查表遇到问题可以先按这个表过一遍场景推荐配置注意事项少量固定选项15个clearable 默认 value不需要 filterable避免多余交互大量静态数据几百条内filterable clearable filter-method用防抖控制筛选频率避免卡顿数据量超大1000条filterable remote clearable必须远程加载分页控制返回条数联动表单clearable 必备 自定义 clear在 clear 中重置下级数据选择后需要快速换选filterable clearable用 clear 触发恢复默认列表中英文混合搜索filter-method 自定义多字段匹配统一转小写中文按包含英文支持模糊这里要特别说一下 remote 模式 clearable 使用时的常见注意点。Element-ui 的 remote 并不会因为 clearable 的点击而自动帮你清空远程搜索结果你必须手动在 clear 里重置 option 列表。我自己实测过在 Element-ui 2.15.x 版本里remote 模式下触发 clear 后如果 remote-method 没有被再次调用下拉面板打开后能看到的还是之前残留的搜索结果用户会一脸懵。配合一个空的默认请求就能很自然地解决。还有一点要留意远程搜索与选中值回显的配合。remote 模式下选项列表实际是动态更新的。一旦你选中了一个值然后再次触发搜索上次选中的那一项极有可能从列表里消失。如果后端没有返回它前端就无法正确展示 label。我做了一个简单的工具函数在拿到搜索结果后主动过滤并检查当前选中项是否在其中如果不在就手动 unshift 到列表头部保证回显永远不出错mergeCurrentOption(targetList, currentValue, allList) { if (!currentValue) return targetList const existing targetList.find(item item.userId currentValue) if (!existing) { const current allList.find(item item.userId currentValue) if (current) { targetList [current, ...targetList] } } return targetList }再补充一个关于 filterable 用户体验的细节加了 filterable 之后默认下拉框的“点击即选中”行为没有变但是输入搜索内容后键盘上下键浏览选项的交互依然可用Enter 键可以选中高亮项。这一点非常方便快捷键用户如果产品经理跟你提“希望支持键盘操作选择”不要再去额外实现告诉他把筛选做做对再引导用户用方向键即可。我从开始接触到真正把 el-select 这两个属性用到顺手大概是在两个完整项目迭代之后。很多坑并不是组件本身的 bug而是没有理解它在“数据流”上的细节什么时候更新值、什么时候触发事件、什么时候重渲染。把这三条线理顺了不管是写后台管理还是前台配置系统都不容易再被用户吐槽下拉框难用了。
返回列表