首页
/ Ant Design AutoComplete「不确定类目」查询模式:用 onSearch 驱动动态选项的完整实现

Ant Design AutoComplete「不确定类目」查询模式:用 onSearch 驱动动态选项的完整实现

2026-09-06 12:15:00作者:史锋燃Gardner

本文基于 ant-design 仓库中的示例 uncertain-category 展开,讲解 AutoComplete 组件如何配合受控 optionsshowSearch.onSearch 实现「不确定类目」查询模式:下拉候选数量随用户输入动态变化的实时搜索场景。读完你可以掌握远程搜索型自动完成框的状态管理方式、关键配置项(popupMatchSelectWidthshowSearch、自定义输入组件)的用法与取值依据,以及从源码层面理解 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;

这段代码的结构可以拆成三层:

  1. 数据层(searchResult,L7-L36):模拟远程搜索接口。给定查询词 query,生成一个数量随机(getRandomInt(5),即 0~5 个)的选项数组——这正体现了「不确定类目」的核心特征:选项数量由查询结果决定,渲染前不可预知。每个选项的 label 不是纯文本,而是一段富内容:左侧是包含可点击链接的「Found {query} on {category}」,右侧通过 flex 布局右对齐显示结果数(100~200 的随机数)。
  2. 状态层(L38-L47):用 useState 保存受控的 optionshandleSearch 在每次输入变化时调用 searchResult 重算候选项;输入为空时清空选项。onSelect 在用户选中某项时触发,参数为选中项的 value
  3. 视图层(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 是否根据输入项进行筛选。函数形式时接收 inputValueoption 两个参数,返回 true 表示符合筛选条件 boolean | function(inputValue, option) true
onSearch 搜索补全项的时候调用 function(value) -

AutoComplete.tsx 的类型定义中,showSearch 被收窄为 SearchConfigfilterOptiononSearchsearchIcon 三个字段的 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> 元素——字符串转为「值即文案」的选项,对象则取 valuetext 字段。新代码应统一使用 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 上。

宽度控制:popupMatchSelectWidthstyle.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 裁剪掉了 loadingmodelabelInValuefilterSort 等与「自由输入」语义冲突的 Select 属性(见 AutoComplete.tsx),并额外扩展了 statuspopupMatchSelectWidthpopupRender 等自动完成专用属性。理解了这层结构后,很多 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,是「输入驱动候选项」这类交互最容易踩的坑:

  1. 受控状态下请勿用 onSearch 管理输入值(中文输入问题)。文档 FAQ 明确说明:请使用 onChange 进行受控管理,onSearch 触发于搜索输入、与 onChange 时机不同,且点击选项时不会触发 onSearch。本示例中 onSearch 只负责「拉取候选」,输入框的值交给 AutoComplete 内部非受控管理,因此不存在此问题;若需要同时受控输入值,应以 onChange 为准。
  2. options 为空时,受控 open=true 也不会显示下拉菜单。AutoComplete 本质是 Input 的扩展,空候选时弹出一个空面板会让用户误以为组件不可操作,因此当 options 为空时 open 属性不生效。示例中 handleSearch 在输入为空时 setOptions([]),下拉随之收起,行为与这一约定一致。

其他工程化建议(基于示例结构的合理延伸):

  • 示例中的 searchResult 是纯本地模拟函数,真实项目替换为服务端请求时,可在 handleSearch 中自行加入防抖、请求竞态取消等逻辑(示例本身未包含防抖实现,按需自行补充);
  • 若候选项需要「输入不区分大小写」匹配本地数据,可参考同目录的 non-case-sensitive.tsx 示例;
  • 候选项为纯本地拼接(如邮箱后缀补全)的场景,可参考 options.tsx,其结构与本示例相同:showSearch.onSearch 更新受控 options
  • 更复杂的下拉内容(如加载态、底部操作区)可通过 popupRender 二次包装原始下拉节点,参考 render-panel.tsx

参考路径

登录后查看全文
热门项目推荐
相关项目推荐