Ant Design AutoComplete「不确定类目」查询模式:用 onSearch 驱动动态选项的完整实现
本文基于 ant-design 仓库中的示例 uncertain-category 展开,讲解 AutoComplete 组件如何配合受控 options 与 showSearch.onSearch 实现「不确定类目」查询模式:下拉候选数量随用户输入动态变化的实时搜索场景。读完你可以掌握远程搜索型自动完成框的状态管理方式、关键配置项(popupMatchSelectWidth、showSearch、自定义输入组件)的用法与取值依据,以及从源码层面理解 AutoComplete 为何能同时支持「自由输入」与「选项下拉」。
什么是「不确定类目」查询模式
ant-design 的交互设计规约 reaction 将「自动完成」类输入交互按查询结果分类的多少划分为两种查询模式(Lookup Patterns):
- 确定类目:用户查询的关键词只会在固定的若干类目中出现(例如只出现在「话题」「问题」「文章」3 种类目中),下拉列表以静态分组呈现,选项结构在渲染前就已确定;
- 不确定类目:用户查询的关键词所属的类目数量不确定,可能 4 个、可能 5 个、可能更多。下拉选项必须由用户当前的输入实时决定,数量与内容都不可预知。
规约中还提到了与两者近亲的「实时搜索」模式:随着用户输入,实时显示搜索结果。示例文档 uncertain-category.md 明确说明,它对应的正是查询模式中的「不确定类目」示例,配套实现见 uncertain-category.tsx。
两种模式在仓库中各有一个官方示例,可对照理解:
| 查询模式 | 示例文件 | 选项来源 | 选项结构 |
|---|---|---|---|
| 确定类目 | certain-category.tsx | 静态写死 | 带 label 标题的嵌套分组(group) |
| 不确定类目 | uncertain-category.tsx | 随输入实时生成 | 扁平列表,数量随机 |
完整示例代码
下面完整收录示例 uncertain-category.tsx 的实现:
import React, { useState } from 'react';
import { AutoComplete, Input } from 'antd';
import type { AutoCompleteProps } from 'antd';
const getRandomInt = (max: number, min = 0) => Math.floor(Math.random() * (max - min + 1)) + min;
const searchResult = (query: string) =>
Array.from({ length: getRandomInt(5) })
.join('.')
.split('.')
.map((_, idx) => {
const category = `${query}${idx}`;
return {
value: category,
label: (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
<span>
Found {query} on{' '}
<a
href={`https://s.taobao.com/search?q=${query}`}
target="_blank"
rel="noopener noreferrer"
>
{category}
</a>
</span>
<span>{getRandomInt(200, 100)} results</span>
</div>
),
};
});
const App: React.FC = () => {
const [options, setOptions] = useState<AutoCompleteProps['options']>([]);
const handleSearch = (value: string) => {
setOptions(value ? searchResult(value) : []);
};
const onSelect = (value: string) => {
console.log('onSelect', value);
};
return (
<AutoComplete
popupMatchSelectWidth={252}
style={{ width: 300 }}
options={options}
onSelect={onSelect}
showSearch={{ onSearch: handleSearch }}
>
<Input.Search size="large" placeholder="input here" enterButton />
</AutoComplete>
);
};
export default App;
这段代码的结构可以拆成三层:
- 数据层(
searchResult,L7-L36):模拟远程搜索接口。给定查询词query,生成一个数量随机(getRandomInt(5),即 0~5 个)的选项数组——这正体现了「不确定类目」的核心特征:选项数量由查询结果决定,渲染前不可预知。每个选项的label不是纯文本,而是一段富内容:左侧是包含可点击链接的「Found {query} on {category}」,右侧通过 flex 布局右对齐显示结果数(100~200 的随机数)。 - 状态层(L38-L47):用
useState保存受控的options,handleSearch在每次输入变化时调用searchResult重算候选项;输入为空时清空选项。onSelect在用户选中某项时触发,参数为选中项的value。 - 视图层(L49-L59):
AutoComplete通过四个关键配置串联起搜索行为——options(受控选项)、showSearch={{ onSearch: handleSearch }}(监听搜索输入)、popupMatchSelectWidth={252}(下拉面板固定 252px 宽)、style={{ width: 300 }}(输入框 300px 宽),children 位置放入自定义输入组件Input.Search。
关键配置项逐项解析
以下参数说明均取自组件 API 文档 index.zh-CN.md,并结合示例中的实际用法展开。
showSearch:搜索配置的承载入口
API 文档中 showSearch 的类型为 true | Object,默认值为 true,其中 Object 形式支持两个字段(见 showSearch 参数表):
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| filterOption | 是否根据输入项进行筛选。函数形式时接收 inputValue、option 两个参数,返回 true 表示符合筛选条件 |
boolean | function(inputValue, option) | true |
| onSearch | 搜索补全项的时候调用 | function(value) | - |
在 AutoComplete.tsx 的类型定义中,showSearch 被收窄为 SearchConfig 中 filterOption、onSearch、searchIcon 三个字段的 Pick 类型。示例选择的是对象形式 showSearch={{ onSearch: handleSearch }},这样每次输入变化都会回调 handleSearch(value),由业务侧决定新的候选项。
需要注意的是 filterOption 的默认值是 true,即组件会对 options 做一次「输入值是否为选项前缀」的本地过滤。示例中的模拟数据 value 均以 query 开头,因此能通过本地过滤;如果接的是真实服务端,返回的候选词不一定包含输入前缀,就应显式关闭本地过滤,写成 showSearch={{ filterOption: false, onSearch: handleSearch }},把匹配逻辑完全交给服务端。
options:数据化选项,受控管理候选项
options 的类型为 { label, value }[],文档特别指出「相比 jsx 定义会获得更好的渲染性能」。示例中它由 useState 管理,是典型的受控数据流:输入 → onSearch → 重算 options → 下拉列表更新。
从源码结构看,options 直接透传给底层 Select 作为选项子节点渲染;而旧写法 dataSource 已标记废弃(开发模式下会触发 warning),其值会在 AutoComplete.tsx 中被逐项转换为 <Option> 元素——字符串转为「值即文案」的选项,对象则取 value 与 text 字段。新代码应统一使用 options。
示例中选项的 label 使用了 ReactNode(flex 布局 + 链接 + 计数文本),说明 label 不限于字符串:任何富文本、图标、布局都可以作为候选项展示内容,而 value 仍保持为纯字符串以便回填输入框与传给 onSelect。
自定义输入组件:children 中的 Input.Search
默认情况下 AutoComplete 内部渲染一个 <Input />。示例在 children 位置传入了 <Input.Search size="large" placeholder="input here" enterButton />,把输入框替换成带搜索按钮的「大尺寸搜索框」,视觉上更贴近站内搜索场景。
源码中这一行为的实现在 AutoComplete.tsx:当 children 恰好是一个合法 React 元素、且不是 Select.Option / OptGroup 时,该元素被识别为 customizeInput,并通过内部 API getInputElement 交给底层 Select 渲染。同时源码在开发模式下有一条 warning(L179-L201):使用自定义输入组件时不要再在 AutoComplete 上设置 size,尺寸应自行控制在被自定义的输入组件上——示例正是把 size="large" 写在 Input.Search 上而非 AutoComplete 上。
宽度控制:popupMatchSelectWidth 与 style.width
popupMatchSelectWidth(API 默认true):控制下拉菜单与选择框的宽度关系。传入数字时表示下拉面板的固定宽度(px),且「默认将设置min-width,当值小于选择框宽度时会被忽略」;示例传入252,即候选面板固定 252px 宽。由于候选项是「链接 + 计数」的较宽富内容,面板宽度与输入框(300px)解耦后排版更从容。style={{ width: 300 }}:AutoComplete 根节点默认随内容收缩,示例显式把输入框定宽为 300px。
文档同时给出了废弃项的迁移对照:dropdownMatchSelectWidth 请使用 popupMatchSelectWidth 替代、dropdownClassName 请使用 classNames.popup.root 替代、onDropdownVisibleChange 请使用 onOpenChange 替代——这些别名在 AutoComplete.tsx 中仍做了合并兼容(如 mergedPopupMatchSelectWidth = popupMatchSelectWidth ?? dropdownMatchSelectWidth),但新代码不应再使用。
onSelect:选中回调
onSelect 在用户选中某项时调用,参数为选中项的 value(示例中仅打印日志)。注意 value 是选项数据中的 value 字段(本例为 query + idx 拼成的字符串),而不是展示用的 label 富文本——这也是 label/value 分离设计的价值:展示与数据解耦。
底层原理:AutoComplete 是一个「可自由输入」的 Select
从源码 AutoComplete.tsx 的渲染出口可以看到,AutoComplete 最终渲染的是 antd 的 Select,并强制传入两个内部约定:
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE}:这是 Select 暴露给 AutoComplete 的内部「combobox 模式」,让 rc-select 允许输入框中的文本不必等于任何已渲染选项的值——这正是 AutoComplete 与 Select 的本质区别:Select 是「在限定可选项中做选择」,AutoComplete 是「带提示的文本输入框,可自由输入」;suffixIcon={null}:去掉 Select 默认的下拉箭头,使外观回归普通输入框。
同时 AutoCompleteProps 通过 Omit 裁剪掉了 loading、mode、labelInValue、filterSort 等与「自由输入」语义冲突的 Select 属性(见 AutoComplete.tsx),并额外扩展了 status、popupMatchSelectWidth、popupRender 等自动完成专用属性。理解了这层结构后,很多 AutoComplete 的行为(如下拉虚拟滚动、键盘导航、焦点管理)都可以直接到 Select 的实现中去寻找答案。
与「确定类目」示例的对比
对照 certain-category.tsx 可以看清两种查询模式在代码形态上的差异:
- 确定类目:
options是模块顶层的静态常量,采用「分组」结构——每个分组是一个{ label: <Title/>, options: [...] }对象,label为分组标题(如 Libraries / Solutions / Articles),下拉中以 group title 形式渲染;不需要showSearch.onSearch,因为候选项与输入无关,始终展示全部固定类目。 - 不确定类目:
options是组件内的 state,每次onSearch触发后整体替换;结构是扁平的{ value, label }[],数量由模拟接口返回决定;下拉面板宽度通过popupMatchSelectWidth={252}固定。
两者的共同点是用 options 数据化配置选项,并都通过 children 定制了输入框外观(确定类目示例用的是普通 Input.Search)。
实战注意事项
以下两点来自组件文档 index.zh-CN.md 的 FAQ,是「输入驱动候选项」这类交互最容易踩的坑:
- 受控状态下请勿用
onSearch管理输入值(中文输入问题)。文档 FAQ 明确说明:请使用onChange进行受控管理,onSearch触发于搜索输入、与onChange时机不同,且点击选项时不会触发onSearch。本示例中onSearch只负责「拉取候选」,输入框的值交给 AutoComplete 内部非受控管理,因此不存在此问题;若需要同时受控输入值,应以onChange为准。 options为空时,受控open=true也不会显示下拉菜单。AutoComplete 本质是 Input 的扩展,空候选时弹出一个空面板会让用户误以为组件不可操作,因此当options为空时open属性不生效。示例中handleSearch在输入为空时setOptions([]),下拉随之收起,行为与这一约定一致。
其他工程化建议(基于示例结构的合理延伸):
- 示例中的
searchResult是纯本地模拟函数,真实项目替换为服务端请求时,可在handleSearch中自行加入防抖、请求竞态取消等逻辑(示例本身未包含防抖实现,按需自行补充); - 若候选项需要「输入不区分大小写」匹配本地数据,可参考同目录的 non-case-sensitive.tsx 示例;
- 候选项为纯本地拼接(如邮箱后缀补全)的场景,可参考 options.tsx,其结构与本示例相同:
showSearch.onSearch更新受控options; - 更复杂的下拉内容(如加载态、底部操作区)可通过
popupRender二次包装原始下拉节点,参考 render-panel.tsx。
参考路径
- 示例描述文档:components/auto-complete/demo/uncertain-category.md
- 示例实现:components/auto-complete/demo/uncertain-category.tsx
- 对照示例(确定类目):components/auto-complete/demo/certain-category.tsx
- 组件源码:components/auto-complete/AutoComplete.tsx
- 组件 API 文档:components/auto-complete/index.zh-CN.md
- 交互规约(查询模式):docs/spec/reaction.zh-CN.md
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00