ant-design AutoComplete 组件实战指南:输入联想的 API 全解与基于 Select 的源码实现剖析
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
本篇围绕 ant-design 的 AutoComplete 组件展开,完整覆盖其适用场景、与 Select 的本质差异、全套 API(含showSearch、语义化 DOM、Methods、Design Token 与 FAQ),并深入仓库源码剖析 AutoComplete 如何通过包装 Select 实现自由输入联想,帮助你在实际项目中正确选型、定制输入框并规避废弃属性的迁移陷阱。
一、什么时候用 AutoComplete
组件官方文档(index.en-US.md)给出的使用场景只有两条:
- 当需要一个输入框而非选择器时(When you need an input box instead of a selector);
- 当需要输入建议或辅助提示文本时(When you need input suggestions or helping text)。
文档同时明确了 AutoComplete 与 Select 的关键词区别:
- AutoComplete:带文本提示的输入框,用户可以自由输入,关键词是input(输入);
- Select:在给定选项中选择,关键词是select(选择)。
从源码结构看,这一"输入优先"的定位直接决定了它的实现方式——AutoComplete 本质是一个扩展了 Input 能力的选择器,而非反过来(这一点在文末 FAQ 中也被官方再次强调)。
二、底层实现:AutoComplete 其实是 Select 的薄封装
阅读 AutoComplete.tsx 可以发现,整个组件的核心渲染只有十几行:
return ( <Select ref={ref} suffixIcon={null} {...omit(props, [ 'dataSource', 'dropdownClassName', 'popupClassName', 'onDropdownVisibleChange', 'onOpenChange', ])} prefixCls={prefixCls} classNames={finalClassNames} styles={finalStyles} mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE as SelectProps['mode']} popupRender={mergedPopupRender} onOpenChange={mergedOnOpenChange} popupMatchSelectWidth={mergedPopupMatchSelectWidth} {...{ // Internal api getInputElement, }} > {optionChildren} </Select> );(见 AutoComplete.tsx)
其中有三个关键设计点:
1. 隐藏的组合框模式
mode被强制设为Select.SECRET_COMBOBOX_MODE_DO_NOT_USE。这是 Select 内部保留的"combobox"模式,让下拉面板以输入联想方式工作(无选中态回显、支持自由输入),但对 AutoComplete 使用者完全透明——AutoComplete 的 props 类型通过Omit显式排除了mode、loading、labelInValue、optionLabelProp等 Select 高级属性(见 AutoCompleteProps 定义)。
2. children 的双重语义:自定义输入框 或 旧版 Option
const childNodes: React.ReactElement[] = toArray(children); let customizeInput: React.ReactElement | undefined; if ( childNodes.length === 1 && React.isValidElement(childNodes[0]) && !isSelectOptionOrSelectOptGroup(childNodes[0]) ) { [customizeInput] = childNodes; } const getInputElement = customizeInput ? (): React.ReactElement => customizeInput! : undefined;(见 AutoComplete.tsx)
children 只有一个元素且不是Select.Option/OptGroup时,它被视为自定义输入框,并通过内部 APIgetInputElement注入给 Select;否则 children 走旧版<AutoComplete.Option>兼容路径。测试用例 index.test.tsx 验证了旧版AutoComplete.Option依然可用,而 index.tsx 中AutoComplete.Option就是Select.Option的别名。
3. 废弃属性的兼容层与告警
源码中集中处理了新旧属性映射(见 AutoComplete.tsx):
const mergedPopupRender = popupRender || dropdownRender; const mergedOnOpenChange = onOpenChange || onDropdownVisibleChange; const mergedPopupMatchSelectWidth = popupMatchSelectWidth ?? dropdownMatchSelectWidth;开发环境下会输出废弃告警,映射关系如下(见 AutoComplete.tsx):
| 废弃属性 | 替代方案 |
|---|---|
dropdownMatchSelectWidth | popupMatchSelectWidth |
dropdownStyle | styles.popup.root |
dropdownClassName | classNames.popup.root |
popupClassName | classNames.popup.root |
dropdownRender | popupRender |
onDropdownVisibleChange | onOpenChange |
dataSource | options |
测试文件 index.test.tsx 对每一条废弃告警都做了断言验证,例如:
expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: AutoComplete] `popupClassName` is deprecated. Please use `classNames.popup.root` instead.', );4. dataSource 到 Option 的转换
旧版dataSource支持三种形态,源码中逐一处理(见 AutoComplete.tsx):
string→<Option key={item} value={item}>{item}</Option>;{ value, text }对象 → 用text作为显示文本、value作为选项值;ReactNode(如<span>元素)→ 原样作为 Option 内容。
测试 legacy dataSource should accept react element option 与 AutoComplete should work when dataSource is object array 分别覆盖了后两种形态;而传入函数等非法类型时,会触发 React 错误日志(见 测试)。
三、基础用法:自由输入 + 联想选项
官方 basic.tsx 演示了非受控与受控两种写法:
const mockVal = (str: string, repeat = 1) => ({ value: str.repeat(repeat), }); const getPanelValue = (searchText: string) => !searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)]; // 非受控 <AutoComplete options={options} style={{ width: 200 }} onSelect={onSelect} showSearch={{ onSearch: (text) => setOptions(getPanelValue(text)), }} placeholder="input here" /> // 受控 <AutoComplete value={value} showSearch={{ onSearch: (text) => setAnotherOptions(getPanelValue(text)) }} options={anotherOptions} style={{ width: 200 }} onSelect={onSelect} onChange={(data: string) => setValue(data)} placeholder="control mode" />要点:
showSearch.onSearch负责生成下拉数据(本地联想或异步请求均可);- 受控模式下用
onChange管理输入值,onSelect单独捕获选项点击——这与文末 FAQ 中"受控模式请用onChange而不是onSearch管理状态"的说明一致; options数组项可以是{ value }、{ label, value }或带分组的嵌套结构。
四、搜索与过滤:showSearch 的两个开关
onSearch 驱动的动态联想
options.tsx 展示了"邮箱后缀补全"场景:
const handleSearch = (value: string) => { setOptions(() => { if (!value || value.includes('@')) { return []; } return ['gmail.com', '163.com', 'qq.com'].map((domain) => ({ label: `${value}@${domain}`, value: `${value}@${domain}`, })); }); }; <AutoComplete style={{ width: 200 }} showSearch={{ onSearch: handleSearch }} placeholder="input here" options={options} />注意当用户输入已含@时清空选项——onSearch返回的options完全决定下拉内容,这是把 AutoComplete 接入远程搜索的标准姿势。
filterOption 驱动的本地过滤
如果选项集是静态的,直接用showSearch.filterOption做本地过滤。non-case-sensitive.tsx 演示了不区分大小写匹配:
const options = [ { value: 'Burns Bay Road' }, { value: 'Downing Street' }, { value: 'Wall Street' }, ]; <AutoComplete style={{ width: 200 }} options={options} placeholder="try to type `b`" showSearch={{ filterOption: (inputValue, option) => option!.value.toUpperCase().includes(inputValue.toUpperCase()), }} />filterOption的完整语义:传true(默认)时按输入值过滤选项;传函数时接收inputValue与option两个参数,返回true的选项保留,否则被排除。
一个值得注意的细节:使用自定义输入框时,AutoComplete 默认不按输入内容过滤数据源——测试 AutoComplete with custom Input render perfectly 输入123后断言三条dataSource全部保留,过滤职责完全交给你的filterOption或onSearch逻辑。
Lookup-Patterns:已知类别 vs 未知类别
官方文档列出了两个经典搜索框模式,仓库中均有对应示例:
- Certain Category(certain-category.tsx):输入内容必定落在预设类别(Library / Solutions / Articles)中。示例用分组 options(
{ label, options }嵌套结构)渲染带分组标题、数量徽标和 "more" 链接的联想面板,并配合classNames={{ popup: { root: ... } }}与popupMatchSelectWidth={500}加宽下拉:
const options = [ { label: <Title title="Libraries" />, options: [renderItem('AntDesign', 10000), renderItem('AntDesign UI', 10600)], }, { label: <Title title="Solutions" />, options: [renderItem('AntDesign UI FAQ', 60100), renderItem('AntDesign FAQ', 30010)], }, ]; <AutoComplete classNames={{ popup: { root: styles.categorySearch } }} popupMatchSelectWidth={500} style={{ width: 250 }} options={options} > <Input.Search size="large" placeholder="input here" /> </AutoComplete>- Uncertain Category(uncertain-category.tsx):搜索结果类别不可预知。示例把
label渲染为"Found query on xxx + 结果数"的富文本,并用<Input.Search enterButton />作为自定义输入框,通过onSearch动态生成结果。
五、自定义输入框(children)
AutoComplete 允许把默认<Input />替换为任意输入元素,官方文档声明其类型为HTMLInputElement | HTMLTextAreaElement | React.ReactElement<InputProps>。custom.tsx 演示了多行文本域:
<AutoComplete options={options} style={{ width: 200 }} onSelect={onSelect} showSearch={{ onSearch: handleSearch }} > <TextArea placeholder="input here" className="custom" style={{ height: 50 }} onKeyPress={handleKeyPress} /> </AutoComplete>实现层面的两个注意点(均有源码/测试佐证):
- 自定义输入时不能传
size。源码在开发环境下会告警:You need to control style self instead of setting size when using customize input.(见 AutoComplete.tsx)——因为size作用于默认 Input 的尺寸类,对自定义输入框无效。 - 组件不会覆盖自定义输入框的 className。测试 should not override custom input className 验证了传入
<Input className="custom" />后,combobox角色节点上保留custom类名。同时,使用了自定义输入框时根节点会额外挂上${prefixCls}-customize类(即ant-select-customize,见 AutoComplete.tsx),可据此写针对自定义形态的样式。 - 无障碍与 RTL 均已覆盖:测试通过
screen.getByRole('combobox')定位输入框(见 index.test.tsx),说明自定义输入后 ARIA 角色依然由 Select 内层统一注入;RTL 测试(rtlTest)也在 index.test.tsx 中执行。
六、完整 API
以下为 index.en-US.md 中 API 表格的完整内容(通用 props 参见文档中的 Common props 章节)。加删除线的条目为废弃属性,建议按第五节"底层实现"中的映射表迁移。
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| allowClear | 显示清除按钮 | boolean | { clearIcon?: ReactNode } | false | 5.8.0 起支持 Object 类型 |
| backfill | 使用键盘导航选中选项时是否回填输入框 | boolean | false | |
| children | 自定义输入元素 | HTMLInputElement | HTMLTextAreaElement | React.ReactElement<InputProps> | <Input /> | |
| classNames | 自定义组件内各语义结构的类名,支持对象或函数 | Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> | - | |
联想数据源,请改用options | DataSourceItemType[] | - | - | |
| defaultActiveFirstOption | 是否默认激活第一个选项 | boolean | true | |
| defaultOpen | 下拉菜单初始打开状态 | boolean | - | |
| defaultValue | 初始选中的选项 | string | - | |
| disabled | 是否禁用 | boolean | false | |
下拉菜单 className,请改用classNames.popup.root | string | - | - | |
下拉菜单与输入框是否同宽,请改用popupMatchSelectWidth | boolean | number | true | - | |
自定义下拉内容,请改用popupRender | (originNode: ReactElement) => ReactNode | - | 4.24.0 | |
| popupRender | 自定义下拉内容 | (originNode: ReactElement) => ReactNode | - | |
下拉菜单样式,请改用styles.popup.root | CSSProperties | - | ||
下拉菜单 className,请改用classNames.popup.root | string | - | 4.23.0 | |
| popupMatchSelectWidth | 下拉菜单与输入框是否同宽,默认设置min-width与输入框一致;数值小于输入框宽度时忽略;设为false会禁用虚拟滚动 | boolean | number | true | |
true时按输入过滤选项;函数形式接收inputValue与option,返回true保留该选项 | boolean | function(inputValue, option) | true | ||
| getPopupContainer | 下拉菜单父节点,默认 body;滚动时定位异常可改为滚动容器 | function(triggerNode) | () => document.body | |
| notFoundContent | 无匹配结果时显示的内容 | ReactNode | - | |
| open | 受控下拉打开状态 | boolean | - | |
| options | 选项列表,比 JSX 写法性能更好 | { label, value }[] | - | |
| placeholder | 输入框占位文本 | string | - | |
| showSearch | 搜索配置 | true | Object | true | |
| status | 设置校验状态 | 'error' | 'warning' | - | 4.19.0 |
| size | 输入框尺寸 | large|medium|small | - | |
| value | 选中项 | string | - | |
| styles | 自定义组件内各语义结构的内联样式,支持对象或函数 | Record<SemanticDOM, CSSProperties> | (info: { props }) => Record<SemanticDOM, CSSProperties> | - | |
| variant | 输入框变体 | outlined|borderless|filled|underlined | outlined | 5.13.0 |
| virtual | 设为false时禁用虚拟滚动 | boolean | true | 4.1.0 |
| onBlur | 离开组件时触发 | function() | - | |
| onChange | 选中选项或输入值变化时触发 | function(value) | - | |
下拉打开时触发,请改用onOpenChange | (open: boolean) => void | - | ||
| onOpenChange | 下拉打开状态变化时触发 | (open: boolean) => void | - | |
| onFocus | 进入组件时触发 | function() | - | |
搜索时触发(顶层已废弃,请用showSearch.onSearch) | function(value) | - | ||
| onSelect | 选中选项时触发,参数为选项值与选项实例 | function(value, option) | - | |
| onClear | 清除时触发 | function | - | 4.6.0 |
| onInputKeyDown | 按键时触发 | (event: KeyboardEvent) => void | - | |
| onPopupScroll | 下拉滚动时触发 | (event: UIEvent) => void | - |
showSearch 子配置
顶层filterOption/onSearch已废弃,官方推荐把它们收进showSearch对象:
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| filterOption | true时按输入过滤;函数形式接收inputValue与option,返回true保留该选项 | boolean | function(inputValue, option) | true |
| onSearch | 搜索时触发 | function(value) | - |
这与源码中的类型声明一致(见 AutoComplete.tsx):showSearch可以是boolean,也可以是{ filterOption, onSearch, searchIcon }的挑拣对象。
校验状态与输入框变体
status="error" | "warning"配合 status.tsx 演示红色/黄色描边,主要用于 Form 集成场景(文档中还提供了form-debug等调试示例验证 Form 内禁用态文本颜色)。variant(5.13.0+)支持outlined(默认)、filled、borderless、underlined四种形态,variant.tsx 中四种变体并列渲染。allowClear自 5.8.0 起支持对象写法自定义清除图标,allowClear.tsx 演示了allowClear={{ clearIcon: <CloseSquareFilled /> }}。
七、语义化 DOM:classNames 与 styles
文档的 "Semantic DOM" 章节引用了 _semantic.tsx 交互式示例。AutoComplete 支持的语义键在源码类型 AutoCompleteSemanticType 中定义:
export type AutoCompleteSemanticType = { classNames?: { root?: string; prefix?: string; input?: string; placeholder?: string; content?: string; popup?: NonNullable<SelectSemanticAllType['classNames']>['popup']; }; styles?: { root?: React.CSSProperties; prefix?: React.CSSProperties; input?: React.CSSProperties; placeholder?: React.CSSProperties; content?: React.CSSProperties; popup?: NonNullable<SelectSemanticAllType['styles']>['popup']; }; };其中popup复用 Select 的弹层语义结构(root/list/listItem)。源码通过useMergeSemantic合并用户传入的语义类名/样式,并为popup配置了_default: 'root'兜底映射(见 AutoComplete.tsx),因此classNames.popup.root能同时接收旧属性popupClassName/dropdownClassName的合并结果:
popup: { root: clsx(popupClassName, dropdownClassName, mergedClassNames.popup.root), list: mergedClassNames.popup.list, listItem: mergedClassNames.popup.listItem, },(见 AutoComplete.tsx)。文档提供的style-class.tsx(6.0.0)示例即为按语义结构定制样式的完整参考。
八、Methods、Design Token 与下拉面板
- Methods:组件实例暴露
blur()(移除焦点)与focus()(获取焦点)两个方法。测试 focus.test.tsx 对焦点行为有专门验证。 - Design Token:文档通过
<ComponentTokenTable component="Select">渲染 Token 表——AutoComplete 直接复用 Select 组件的 Design Token 体系,这与它"包装 Select"的实现完全对应,调主题时按 Select 的 Token 配置即可。 - 内部面板:index.tsx 通过
genPurePanel生成了AutoComplete._InternalPanelDoNotUseOrYouWillBeFired,仅用于文档调试场景(如 render-panel.tsx 中脱离宿主输入框单独渲染下拉面板),生产代码不应使用。
九、FAQ(官方高频问题)
为什么受控模式下onSearch配合输入法合成(composition)效果不佳?
请用onChange管理受控状态。onSearch是搜索输入回调,与onChange语义不同;且点击选项不会触发onSearch。官方文档关联了社区问题 #18230 与 #17916 作为背景参考。
为什么open受控为 true 时,options 为空就不显示下拉?
AutoComplete 本质是 Input 的扩展。当options为空时,展示空面板容易让用户误以为组件不可用(实际上仍可以输入文本)。为避免误导,open必须与options搭配使用:open={true}且options为空时不会渲染下拉菜单。
十、小结:选型与落地建议
结合文档与源码(AutoComplete.tsx、index.tsx、测试集)可以得到几条工程结论:
- 能自由输入选 AutoComplete,只能选择选 Select。AutoComplete 内部以
SECRET_COMBOBOX_MODE_DO_NOT_USE模式复用 Select 全部能力(虚拟滚动、分组、语义化样式),但屏蔽了多选等无关 props; - 数据优先用
options,dataSource与顶层filterOption/onSearch均已废弃,源码中有明确的 deprecated 告警表可用于对照迁移; - 本地联想用
showSearch.filterOption,远程联想用showSearch.onSearch驱动options;受控模式用onChange同步输入值; - 样式定制走
classNames/styles语义键(root/input/popup.root等),旧dropdownClassName、dropdownStyle等属性只会收到告警; - 自定义输入框时:不传
size、自行控制尺寸样式,注意默认不再按输入过滤数据源。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考