news 2026/10/1 1:03:59

Ant Design Select 可搜索可输入下拉选择实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ant Design Select 可搜索可输入下拉选择实践

1. 一个"能打字的下拉框",为什么值得单独拎出来讲

a-select大概是 antd 里被用得最多、也最容易被低估的组件之一。表面上看它就是个下拉框,但真正落到业务里,需求往往没那么干净:选项里没有用户想要的那一项,怎么办?数据量几千条根本没法一次性渲染,怎么办?用户想按中文名搜,但后端只给了一串 id,怎么办?这时候就得把a-select的"可搜索 + 可输入 + 可下拉选择"这几副面孔全部调动起来。

我先把它到底能干什么说清楚。antd 的 Select 组件在底层其实一直存在一个真实的输入框,只是在不同模式下它被设置成只读、宽度为零或者允许编辑。所谓"既可手动输入又可下拉选择",本质上是让这个输入框在用户敲键盘时拿到焦点,把输入内容作为搜索词去过滤候选项,同时保留点击/键盘上下键选中列表项的能力。它解决的核心问题是:在枚举值不完全可控、或者选项规模较大的场景下,让用户既能快速定位已有内容,又不必被固定选项卡死。

这篇内容适合谁看?如果你是刚接手后台管理系统的前端,手上正好有个"客户名称可以选也可以自己写"的需求,那这篇能让你少调半天;如果你已经写过不少a-select,但一直被"回显成 id""列表闪回旧数据""打开了 showSearch 却打不了字"折磨,这篇里的排查表和踩坑记录应该能直接对上你的症状。我会按"需求拆解 → 属性原理 → 受控写法 → 自由输入 → 远程搜索 → 问题排查 → 业务封装"这条线走一遍,代码都能直接复制去改。

2. 需求拆解:你到底要的是哪一种"输入"

很多同学一上来就写showSearch,结果发现表现不对,然后又加mode、加filterOption,属性堆了一堆还是不对味。问题出在没分清几种能力的边界。antd 官方文档里 Select 的能力其实是分层的,把它们混在一起看,就容易组合出互相打架的配置。

2.1 四种能力,对应四个不同场景

先做一个区分,这四件事听起来像,做起来完全不是一回事:

能力用户能做什么典型 API适用场景
普通下拉选择只能从列表里点默认行为枚举固定,如性别、状态
搜索过滤打字筛已有选项,不能新增showSearch+filterOption城市、部门、已有用户
自由输入新增打字后回车,值可以不在列表里mode="tags"打标签、关键词录入
远程搜索打字触发请求,选项来自接口showSearch+onSearch数据量大、候选依赖上下文

你看,只有第二种和第三种才是"既输入又选择"。第一种是纯选择,第四种是把输入当成了搜索指令而不是最终值。判断标准很简单:用户敲进去的那串字符,最终会不会作为表单值提交给后端?会,那就是自由输入;不会,只是用来找选项,那就是搜索。

2.2 数据量决定了是本地过滤还是远程搜索

这个判断直接影响架构。经验值是:选项总数在 200 条以内,全部塞进options做本地过滤最省事,用户体验也最好,因为没有任何网络延迟,敲一个字立刻出结果。超过 500 条,本地过滤就开始拖慢渲染了,尤其是 Select 的虚拟滚动虽然能扛住渲染压力,但每次输入都对上千个 option 跑一遍filterOption函数,在低端机上会有明显卡顿。

再往上走到几千、几万条,就只能走远程搜索。这时候options只保存当前这一页的结果,输入变化触发请求,回填新的列表。代价是你要自己处理防抖、竞态、loading 和"没有匹配结果"的空状态,这几个东西没有一个是能白拿的。我后面会专门开一节讲。

2.3 一个常见的误判:把"用户可输入"理解成"用户可以随便输"

这是需求沟通里最容易翻车的地方。业务方说"这里允许手动输入",他心里的意思可能只是"选项太多,让用户能打字找",而不是"用户可以填一个数据库里没有的值"。这两者做出来的效果差异巨大:前者你只需要showSearch,后者你要处理新值的校验、大小写归一化、去空格、和后端确认这个新值能不能入库。

我的做法是问三个问题:输入的值要不要存进数据库?如果存,是不是要同步创建一条基础数据?如果用户输入了一个明显错误的拼写,谁负责纠正?这三个问题的答案决定了你到底用showSearch还是mode="tags",还是干脆上AutoComplete。别嫌麻烦,这一步问清楚,后面能省掉一次返工。

3. 属性原理:打开 showSearch 之后到底发生了什么

知道要什么之后,接下来得知道组件内部是怎么运作的。antd 的 Select 是基于rc-select封装的,理解 rc-select 的几层结构,很多"玄学问题"就变成必然结果了。

3.1 输入框一直都在,只是被藏起来了

Select 渲染出来的 DOM 结构大致是这样的几层:最外层是一个div.ant-select,里面包含一个选择器区域div.ant-select-selector,选择器里面又放着若干span.ant-select-selection-item(已选中的项)和一个input.ant-select-selection-search-input。这个 input 就是那个"隐藏的输入框"。

在普通单选模式下,这个 input 被设置了readonly并且opacity: 0、宽度极小,所以你点上去只能展开下拉,打不了字。一旦你加上showSearch,antd 会把 input 的 readonly 去掉,用户可以聚焦输入,输入的内容会作为搜索词传给内部的过滤逻辑。

理解这一点很重要,因为它解释了几个经典现象:为什么用 CSS 强行改ant-select-selection-search-input的宽度会出现光标位置诡异?为什么在某些 UI 框架里 Select 聚焦后按退格会删掉已选值?都是因为这个隐藏 input 的焦点状态在起作用。

3.2 默认按 value 过滤,这才是搜不到中文的元凶

showSearch打开之后,默认会走filterOption的默认实现,而这个默认实现匹配的字段由optionFilterProp决定,它的默认值是value,不是label。

这就解释了一个非常高频的疑问:我明明选项上显示的是"上海",为什么输入"上海"搜不出来?因为你的options大概长这样:

const options = [ { value: '310000', label: '上海' }, { value: '110000', label: '北京' }, ];

用户输入的是"上海",但过滤逻辑拿去和value(也就是310000)做匹配,自然一条都对不上。解决办法有两种,最简单的就是显式指定过滤字段:

<Select showSearch optionFilterProp="label" options={options} placeholder="请选择或输入城市名" />

另一种是自己写filterOption,把匹配逻辑完全接管。我一般更倾向后者,因为业务里经常要支持拼音首字母、简繁体、去空格这类需求,写死用label迟早要改。

3.3 自定义 filterOption:把匹配规则握在自己手里

先看一个稳妥的通用写法,兼容 v4 和 v5,因为两个大版本里filterOption收到的option参数形态略有差异——v4 里可能是带children的 React 元素,v5 里用options属性传入时则是带label的普通对象:

const buildFilter = (input, option) => { // v5 用 options 传入时 option.label 可用,v4 children 形式做兜底 const text = option?.label ?? option?.children ?? option?.value ?? ''; return String(text) .toLowerCase() .replace(/\s+/g, '') .includes(input.toLowerCase().replace(/\s+/g, '')); };

这个写法做了三件事:兼容两个版本的取值路径、统一转小写做大小写不敏感匹配、去掉空格避免用户手抖多打了一个空格就搜不到。别小看最后一条,用户复制粘贴公司全称的时候,前后带空格是家常便饭。

如果要支持拼音首字母,可以引入pinyin-pro这类库,把 label 转成全拼和首字母两份缓存起来,过滤时一起比对:

import { pinyin } from 'pinyin-pro'; const pinyinCache = new Map(); const getPinyinText = (label) => { if (pinyinCache.has(label)) return pinyinCache.get(label); const full = pinyin(label, { toneType: 'none', type: 'array' }).join(''); const short = pinyin(label, { pattern: 'first', toneType: 'none', type: 'array' }).join(''); const result = `${full}${short}`; pinyinCache.set(label, result); return result; };

缓存这一步是必须的,否则每次按键都对所有选项重新算一遍拼音,几千条数据下卡顿会非常明显。用Map做进程内缓存足够了,因为选项列表在一次会话里通常不会变。

3.4 几个容易被忽略的配套属性

光有filterOption还不够,下面这几个属性是搭配使用的,缺了哪个体验都会打折:

  • allowClear:让用户可以清空已选。不加的话,一旦选错就得靠重新选择覆盖,体验很别扭。
  • notFoundContent:搜索无结果时的提示文案。异步搜索场景下建议先设为null,等请求回来再决定要不要显示"无数据",否则会出现"正在加载 → 无数据 → 有数据"的闪烁。
  • defaultActiveFirstOption:默认高亮第一项。在允许自由输入的场景下,这个属性要谨慎,因为用户敲完直接回车可能误选高亮的项而不是提交自己输入的值。
  • filterSort:对过滤后的结果排序,比如把前缀匹配的排到前面。数据量大时配合远程搜索用得多。
  • getPopupContainer:把下拉挂到指定的父节点上。在 Modal 或 Drawer 里使用时必配,不然滚动时下拉框会飘在外面。

4. 受控与非受控:状态放在哪里决定了后面所有坑

Select 同时支持受控和非受控。非受控模式下用defaultValue和defaultOpen,组件自己管自己的状态;受控模式则必须由外部给出value和onChange。什么时候用哪个,我的建议很简单:只要值需要参与联动、需要被重置、需要和表单校验挂钩,就一律受控。

4.1 非受控的省事与代价

非受控看起来省代码,写个defaultValue={1}就完事了。但它有两个绕不过去的问题。第一是重置,如果页面上有个"清空筛选"按钮,非受控的 Select 你没法从外部把它变回空值,只能靠key强刷,而强刷会连带把下拉状态、滚动位置全丢掉,体验很糙。第二是回显,编辑页面拿到后端数据后要给 Select 赋值,非受控的defaultValue只在首次挂载时生效,数据是异步回来的时候就完全失效了。

所以真正做业务,还是老实受控。antd 的 Form 会把 Select 包一层,Form.Item自动注入value和onChange,其实也是受控模式,只是它帮你写了。

4.2 labelInValue:解决"后端要 id、前端要显示名字"

这是 Select 里我认为最值得单独讲的一个属性。默认情况下value就是选项的value字段(通常是 id),选中之后组件内部会从options里反查出对应的label来显示。如果反查不到,你看到的就会是一串冷冰冰的 id——这就是很多人遇到的"回显成 id"问题。

labelInValue会把value的形态从标量改成{ value, label }对象:

<Select labelInValue showSearch optionFilterProp="label" options={options} value={selected} onChange={(val) => setSelected(val)} /> // onChange 收到的 val 形如:{ value: '310000', label: '上海' }

这样即使options里暂时没有这一项,显示层依然能拿到label正常渲染,不会出现 id。代价是提交给后端时你要自己把val.value拆出来,如果你直接JSON.stringify整个表单,记得在提交前做一层转换。

4.3 事件触发顺序:一个必须实测才知道的细节

Select 提供了好几个回调,onChange、onSelect、onDeselect、onSearch、onInputKeyDown、onDropdownVisibleChange。它们看起来各管一摊,但实际有触发顺序依赖,踩过坑的人才清楚。

我实测下来的顺序是这样的(单选模式,用户点击某个选项):

步骤触发的事件说明
1onSelect(value, option)选中某项时最先触发,携带完整 option
2onChange(value, option)值变化,受控组件主要靠它
3onSearch('')搜索词被清空
4onDropdownVisibleChange(false)下拉关闭

这个顺序解释了一个常见 bug:你在onSearch里做了远程请求,结果用户一选中,onSearch('')触发了,又发了一次空关键词的请求,把列表刷成了默认数据。解决办法是在onSearch里判断空字符串直接return,或者和onSelect搭配用一个标记位忽略这次清空。

另外onInputKeyDown可以拿到原始键盘事件,用来监听回车。这个后面讲自由输入时会重点用。

5. 让用户输入一个库里没有的值

终于来到核心需求。分两种情况,一种是多选打标签,一种是在单选场景下模拟自由输入。

5.1 mode="tags":最省事的自由输入

mode="tags"是 antd 给的现成方案:用户在输入框里敲内容,回车之后就会新增一个 tag,即使这个值不在options里。底层的行为是,新值被当作{ value: 输入内容, label: 输入内容 }加进已选列表,并触发onChange。

<Select mode="tags" style={{ width: 400 }} placeholder="输入后回车即可新增" options={tagOptions} tokenSeparators={[',', ',']} onChange={(vals) => setValues(vals)} maxTagCount="responsive" />

几个实操要点。tokenSeparators让用户可以用逗号批量粘贴,这在导入关键词的时候特别好用,用户从表格里复制一列粘进来,直接变成多个 tag。maxTagCount="responsive"会在空间不够时把多余的 tag 折叠成 "+N",避免标签多了把输入框撑成两行甚至三行。

但tags有几个副作用要提前知道。第一,它只能多选,不能单选,如果你的需求是"单选 + 允许自由输入",用tags就会让用户能选多个值,需要在onChange里截取最后一个,很别扭。第二,输入过程中会实时新增临时 tag 展示,视觉上有点跳。第三,空格和特殊字符的处理需要自己兜底,用户输入" "这种纯空格,回车后也会变成一个 tag。

5.2 单选场景的自由输入:手动接管回车事件

单选又要能自由输入,tags就不合适了。我的做法是用受控的searchValue加上onInputKeyDown手动实现。核心思路是:把用户输入的搜索词单独存一份状态,回车时判断这个搜索词在现有选项里有没有匹配,没有就把这个值当作最终值提交,并且把它补进options里,让显示层能找到 label。

const [options, setOptions] = useState(initialOptions); const [searchValue, setSearchValue] = useState(''); const [value, setValue] = useState(null); const handleInputKeyDown = (e) => { if (e.key !== 'Enter') return; const input = searchValue.trim(); if (!input) return; const matched = options.some( (opt) => String(opt.label).toLowerCase() === input.toLowerCase() ); if (!matched) { const newOption = { value: `custom_${Date.now()}`, label: input, isCustom: true }; setOptions((prev) => [...prev, newOption]); setValue(newOption.value); } // 回车之后把搜索词清掉,避免残留导致列表被过滤 setSearchValue(''); }; <Select showSearch allowClear value={value} searchValue={searchValue} filterOption={buildFilter} options={options} onSearch={(v) => setSearchValue(v)} onInputKeyDown={handleInputKeyDown} onChange={(v) => setValue(v)} placeholder="选择或输入后按回车" />

这里有个细节值得说:custom_${Date.now()}这个伪 id 是为了让 Select 内部能唯一识别这条选项。如果你直接把用户输入当 value,两次输入同样的内容就会被认为是同一个值,虽然多数情况下没问题,但一旦需要区分"用户手输"和"从列表选"这两种来源,就区分不出来了。我给自定义项打上isCustom: true标记,提交时按标记走不同接口,这样后端也知道该不该自动建基础数据。

5.3 combobox 弃用之后,替代路径是什么

老版本的 antd 有mode="combobox",专门干单选自由输入这件事。但它在后续版本里被标记为不推荐使用,官方建议用AutoComplete替代。不过我实际用下来,AutoComplete和 Select 在手感上差异不小——AutoComplete 没有明显的下拉箭头和选中态样式,用户一看不一定知道这里能点开。

如果你的交互设计上就是要"长得像下拉框",我建议还是用 Select 加手动接管回车这条路,也就是上面那段代码。它虽然多写十几行,但视觉和交互完全可控,也不会因为版本升级被弃用的 API 影响。要不要为了省代码去用 AutoComplete,取决于你的设计稿更偏向"输入建议"还是"下拉选择"。

6. 远程搜索:防抖、竞态、回显三件套

数据量大起来之后,本地过滤这条路就走不通了。远程搜索的技术栈不复杂,但有三个坑,几乎是每个人都要踩一遍的。

6.1 防抖不是可选项,是必需品

用户每敲一个字符都发一次请求,输入"上海市浦东新区"就是八次请求。接口扛不扛得住是一回事,更麻烦的是网络返回顺序无法保证,界面会跳来跳去。所以防抖必须做,我一般用 300 到 500 毫秒,取决于接口的平均响应时间。如果接口稳定在 100 毫秒内返回,300 毫秒就够了;如果接口经常要 300 毫秒以上,就调到 500 毫秒,不然用户打字停下来之后还要等。

import { useRef, useState, useCallback } from 'react'; import debounce from 'lodash/debounce'; const useRemoteOptions = (fetcher) => { const [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); const latestSeq = useRef(0); const search = useCallback( debounce(async (keyword) => { const seq = ++latestSeq.current; setLoading(true); try { const list = await fetcher(keyword); // 关键:只接受最后一次请求的结果 if (seq === latestSeq.current) { setOptions(list.map((it) => ({ value: it.id, label: it.name }))); } } finally { if (seq === latestSeq.current) setLoading(false); } }, 400), [fetcher] ); return { options, loading, search }; };

注意debounce的创建位置。如果直接在函数组件体里写debounce(...),每次渲染都会生成一个新的防抖函数,防抖就完全失效了——这是 React 里用防抖最经典的坑。要么用useCallback包一层,要么用useRef存住它。上面用的是useCallback加依赖数组的方案,useMemo也行。

6.2 竞态:为什么列表会闪回旧结果

看上面代码里的latestSeq,这个就是解决竞态的。想象一个场景:用户输入"上",请求 A 发出;用户很快改写为"上海",请求 B 发出。如果 A 比 B 晚返回,那么 A 的结果会覆盖掉 B 的结果,用户看到的就是"上"对应的列表,而他输入框里写的是"上海"。这种 bug 在本地网络下不容易复现,一到网络抖动或者跨地域访问就频发,测试同学一提就是"偶现",非常难查。

解决思路就是给每次请求打一个自增序号,返回时比对是不是最新的那次,不是就丢弃。用AbortController取消请求也可以,但要注意不是所有请求库都支持取消,而且取消请求本身也有兼容性顾虑,序号比对是零依赖的方案,我一般优先用它。

6.3 回显问题:value 有值,界面却显示 id

编辑页面最典型:后端返回{ cityId: 310000 },你把它赋给 Select,页面上显示的却是310000而不是"上海"。原因就是组件内部从options里反查 label,而此时options里只有第一页的远程搜索结果,压根没有这一项。

三种解法,按推荐度排列。第一种是用labelInValue,直接在赋值时把 label 一起带上,前提是后端接口返回了名称字段,这个最干净。第二种是初始化时单独调一次详情或者批量查询接口,把已选中的这几项塞进options的头部,不参与搜索过滤但用于显示。第三种是给选项设置optionLabelProp指向一个已存在的字段,但这只在 options 里确实有这条数据时才有用。

我自己更常用第二种和第一种的组合:接口返回名称就直接用labelInValue;接口只给了我 id,就发一次批量查询把 label 补全。千万不要用"先把 value 清空,等选项加载完再赋值"这种绕法,会造成表单闪烁,而且表单校验会在中间那一刻报"必填"错误。

7. 踩坑实录与问题速查

前面讲的都是原理和方案,接下来是我在实际项目里真正踩过的坑,以及一份可以直接对着查的速查表。

7.1 常见问题速查表

现象大概率原因处理方式
加了 showSearch 还是不能打字disabled或mode冲突,或外层 CSS 覆盖了 input 的 opacity检查 disabled、检查自定义样式是否改了.ant-select-selection-search-input
输入中文搜不到,输入数字能搜到optionFilterProp默认是value设为label,或自定义filterOption
选完之后搜索词没清掉,下次打开列表被过滤受控了searchValue但没在onChange里清空在onChange/onSelect里同步setSearchValue('')
下拉列表在 Modal 里位置乱飘下拉挂到了 body,滚动定位失效配置getPopupContainer={(node) => node.parentNode}
异步搜索时先闪"无数据"再出结果notFoundContent默认文案在 loading 期间就渲染了用notFoundContent={loading ? <Spin size="small" /> : null}
列表项几百条之后展开卡顿自定义 option 渲染里做了重计算用useMemo缓存 option 节点,或使用listHeight调优虚拟滚动
回车选中的不是自己输入的值defaultActiveFirstOption默认高亮第一项自由输入场景下设为false
编辑页显示 id 而不是名称options 里反查不到对应 label用labelInValue或预加载已选值

7.2 几个我印象最深的坑

第一个是虚拟滚动。数据量到了几千条,antd 默认开启虚拟滚动,只渲染可视区域内的选项。这本身是好事,但如果你在代码里通过document.querySelector去找某个 option 的 DOM 做操作,会发现找不到——因为它根本还没被渲染出来。我当时的场景是要在打开下拉时自动滚动到某个已选中的项,绕了很久最后用的是 Select 的 ref 上的scrollTo方法,而不是去操作 DOM。

第二个是showSearch和labelInValue一起用时的事件参数。开启labelInValue之后,onChange第一个参数从标量变成了对象,但onSelect的第二个参数option依然保留着原始结构,两者形态不一样。我在做日志埋点的时候混用过这两个参数,结果统计出来的 user_id 全是[object Object],排查了半天。

第三个是 React 18 的严格模式。开发环境下组件会渲染两次,防抖函数如果创建方式不对,会出现"请求发了两次""loading 状态闪一下"这类只在开发环境出现的现象。遇到这种情况先别急着改业务逻辑,确认一下是不是重复渲染导致的副作用,用useRef存防抖函数就能解决。

第四个和输入法有关。中文输入法在拼字阶段会触发compositionstart和compositionend事件,期间的input事件拿到的可能是拼音字母而不是最终汉字。如果在这期间就发搜索请求,用户会看到一堆莫名其妙的英文结果。稳妥的做法是监听onCompositionStart和onCompositionEnd,在组合输入期间屏蔽搜索触发,等compositionend之后再发。

注意:中文输入法的组合输入问题在桌面端浏览器上普遍存在,做远程搜索时一定要处理,否则用户搜索中文的体验会非常差。

8. 封装成业务组件,才是真正的收尾

把上面这些拼在一起,你会发现每个页面都写一遍太累,尤其是远程搜索那一套防抖加竞态处理,重复写容易漏。我的习惯是抽两个组件出来,一个管本地过滤,一个管远程搜索。

8.1 本地过滤的封装要点

本地版本相对简单,主要做三件事:统一注入showSearch和allowClear,把optionFilterProp设成label,以及把拼音匹配逻辑内置进去。用一个useMemo缓存过滤函数,避免每次渲染都新建。props 上保留options和onChange的原型,这样业务侧的感受和用原生 Select 完全一致,学习成本为零。

这里有个设计取舍值得说:要不要把拼音匹配做成内置的?内置的好处是所有用到的地方都能拼音搜,坏处是引入了一个第三方依赖,包体积会涨。我的做法是把它做成可选 props,默认关闭,需要拼音搜索的页面显式打开。这样不会让整个项目的依赖被迫增加,也让使用者清楚知道自己打开了什么。

8.2 远程搜索组件必须暴露的三个状态

远程版本要暴露loading、options、onSearch的透传,同时要处理"初始值回显"这个高频难题。我在组件内部加了一个initialValue的 prop,传进来的时候自动合成一条 option 塞到列表头部,标记为hiddenInFilter,这样它不会参与搜索匹配,但能被反查出来用于显示。

另外,远程搜索的下拉里最好加一个"没有找到想要的结果?"的提示区,用的是dropdownRender(新版本里改名成了popupRender)往列表底部插内容。用户可以点一下直接把他输入的关键词作为新值提交,比让他自己琢磨"我该不该回车"要友好得多。这个交互在 CRM、工单这类系统里特别常见,客户名称不在库里的时候,直接创建。

8.3 性能与可访问性上还有两件小事

性能方面,onChange里做重活儿要小心。有些项目会在onChange里立刻发一个联动请求,用户快速切换选项时会连续发好几个,建议加一层防抖或者用请求序号做保护。option 的渲染函数也要缓存,如果每个 option 里都塞了 Icon 或者复杂节点,几百条数据下的渲染开销不小。

可访问性方面,Select 默认支持键盘操作,Tab 聚焦、上下键切换、回车选中都是内置的。但有一个容易被忽略的点:如果你自定义了 option 的内容结构,比如在 label 里加了颜色标记的 span,读屏软件可能读不出完整信息,最好给 Select 加上aria-label,或者确保 option 的label属性是纯文本描述,视觉展示和语义信息分开处理通常是更好的选择。

9. 最后几句经验

我做过的项目里,a-select出问题的场景高度集中在那么几个地方:搜索字段配错、受控状态没同步、异步数据竞态、回显缺 label。这四个问题占了八成以上。所以每次写这个组件之前,我会先在心里过一遍这四个点,基本上能提前避开。

还有一个习惯分享给你:在项目里给 Select 加一层薄薄的业务封装,把optionFilterProp、allowClear、getPopupContainer这些"每次都要写但每次都会忘"的属性设成默认值,业务侧只关心options和onChange。这样能避免团队里每个人写出来的 Select 行为不一致——同一种选择框,有的能清空有的不能,有的能搜中文有的只能搜 id,这种不一致带来的沟通成本远比多写一个组件高。等你把这一层封好了,下次遇到"既要手输又要下拉"的需求,你会发现真的只是传个 props 的事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 1:01:43

Vue3 手写滑动验证组件:从拖拽原理到后端安全校验全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:01:32

135k代驾小程序源码v1.2.24:从跑通到二次开发实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:01:19

暗区突围MPX火神枪管修脚弹配置:高射速秒杀六级甲攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:01:07

用Rebiber自动将arXiv预印本引用转换为正式发表版BibTeX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 23:53:04

Vue 3.0 新手入门指南:用 TaoToken 统一 Key 打通 AI 辅助开发配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 23:51:14

PLC编程语言全解析:从梯形图到ST的选型与调试实战指南

干PLC这行十几年&#xff0c;被问得最多的一个问题就是&#xff1a;“PLC编程到底难不难&#xff1f;”每次我都反问一句&#xff1a;“你会不会看电路图&#xff1f;”对方的眼神基本就出卖了他自己。其实PLC的底层逻辑并不神秘&#xff0c;它的核心就是一套把继电器电路“翻译…

作者头像 李华