鸿蒙新特性——Search 搜索组件详解
2026/6/27 2:36:15 网站建设 项目流程

一、引言

搜索是移动端应用最基础的功能入口之一。从 App Store 的搜索标签到微信通讯录的找人功能,从电商 App 的商品搜索到知识库的文章检索——搜索框是用户与海量内容之间的桥梁。在 HarmonyOS NEXT 之前,开发者若要实现一个搜索功能,需要自行组合 TextInput + Image + Button,手动管理焦点状态和搜索逻辑——代码分散且交互细节难以统一。

ArkUI 在 API 10 中新增了Search组件,将搜索框所有元素封装为一个整体:自带搜索图标、占位提示、搜索按钮和自动焦点管理。开发者只需声明初始值和占位文字,再配合onChange(实时输入)和onSubmit(提交搜索)两个事件,就能获得完整的搜索交互体验。

本文通过一个本地知识库搜索Demo 深入讲解 Search 组件的核心用法:如何区分onChange实时过滤和onSubmit确认搜索?如何实现搜索历史记录?如何处理空结果状态?以及如何将 Search 组件融入完整的搜索业务流程。

阅读完本文,你将能够:

  • 使用 Search 组件替代手动的搜索栏布局
  • 区分onChange(实时过滤)和onSubmit(确认搜索)两种事件模式
  • 实现搜索历史的记录、去重和数量管理
  • 处理搜索过程中的三种视图状态(初始、有结果、无结果)
  • 将 Search 组件与 ForEach 列表过滤组合为完整的搜索页

二、Search 组件 API 总览

2.1 构造函数参数

Search({placeholder:'搜索文章...',value:this.query})
参数类型说明
placeholderstring占位提示文字。当搜索框为空时显示,引导用户输入
valuestring搜索框的当前文本值。与@State绑定可实现双向数据流
iconstring搜索图标。默认为系统搜索图标,可传入 Symbol Glyph 名称自定义

placeholder是用户体验的关键——好的占位文字不仅提示"这里能搜索",还提示"能搜什么"。例如"搜索文章标题、内容…"明确告知搜索范围,比"请输入关键词"更有信息量。

value是 Search 组件的核心绑定值:传入@State query后,用户每次输入都会更新query,同时外部修改query(如点击搜索历史标签)也会同步反映到搜索框中。这是典型的双向数据流模式。

2.2 属性方法

searchButton(value: string)

设置搜索按钮的文字,默认显示"搜索":

Search({placeholder:'搜索...',value:this.query}).searchButton('搜索')// 搜索按钮文案//.searchButton('Go') // 也可以使用简短文案//.searchButton('') // 空字符串隐藏搜索按钮

搜索按钮显示在搜索框右侧,点击后触发onSubmit事件。如果不希望显示搜索按钮(纯实时过滤场景),可以传入空字符串。但保留搜索按钮能给用户一个明确的"执行搜索"的操作目标,推荐保留。

width / height

控制搜索框尺寸:

Search({placeholder:'搜索...',value:this.query}).width('100%')// 占满父容器宽度.height(40)// 推荐 36-44vp

在移动端应用中,搜索框通常占满屏幕宽度。高度推荐 36-44vp——足够容纳手指点击,但不过度占用垂直空间。

backgroundColor / borderRadius

控制搜索框背景色和圆角:

.backgroundColor('#F2F3F5')// 浅灰背景.borderRadius(8)// 圆角

2.3 事件回调

onChange(callback: (value: string) => void)

搜索框内容变化时触发。用户每次输入/删除一个字符都会触发:

Search({placeholder:'搜索...',value:this.query}).onChange((value:string)=>{this.query=value;// 实时过滤逻辑...})

onChange是实时过滤(filter-as-you-type)的核心事件。在本地搜索场景中(数据已在本地),每次输入后立即过滤列表,让用户无需点击搜索按钮就能看到结果更新。这种体验比"输入 → 点搜索 → 看结果"少一步操作,在本地搜索中推荐使用。

onSubmit(callback: (value: string) => void)

用户按下键盘回车键或点击搜索按钮时触发:

Search({placeholder:'搜索...',value:this.query}).onSubmit((value:string)=>{// 提交搜索 → 保存历史 + 远程查询})

onSubmit用于需要"确认"的场景——远程搜索(网络请求)、保存搜索历史、触发搜索埋点等。即使主搜索流程使用onChange实时过滤,onSubmit也可以用来保存搜索历史,两者不矛盾。

2.4 onChange 与 onSubmit 的选择

场景推荐事件理由
本地数据过滤onChange即时反馈,无需额外点击
远程 API 搜索onSubmit避免每次输入触发网络请求
搜索历史记录onSubmit只有"确认"的搜索才值得记录
过滤 + 历史都做都用onChange 过滤 + onSubmit 保存历史

本地搜索是最常见的 Search 使用场景——文章列表、通讯录、设置项等。数据量通常在几百到几千条之间,客户端过滤完全可行。远程搜索用于数据量极大或数据不在本地的场景(如电商商品搜索、地图 POI 搜索)。

三、Demo 设计:本地知识库搜索

3.1 功能概述

Demo 模拟一个本地知识库搜索页面,内置 10 篇 ArkUI 技术文章。用户输入关键词后实时过滤文章列表(匹配标题、摘要和分类)。搜索历史记录在本地状态中,最多保留 8 条,支持点击历史标签快速搜索和一键清除。

10 篇文章覆盖 ArkUI 开发的常见主题:

文章分类关键词
ArkUI声明式UI范式详解ArkUI基础声明式、容器、布局
@State与@Prop状态管理实战状态管理@State、@Prop、装饰器
页面路由与导航设计导航路由router、pushUrl、导航
动画曲线与交互反馈动画特效animateTo、Curve、动画
HTTP请求与数据缓存策略网络请求HTTP、缓存、请求
List与Grid高性能渲染列表渲染List、Grid、LazyForEach
自定义组件与@Builder复用组件化@Builder、自定义组件
手势识别与事件处理机制交互事件手势、事件、Pan
主题定制与样式系统样式主题Theme、深色模式、样式
性能优化与调试技巧精讲性能优化性能、优化、SmartPerf

3.2 数据结构

interfaceArticleItem{title:string;// 标题category:string;// 分类summary:string;// 摘要date:string;// 日期}@Statequery:string='';@StatefilteredArticles:ArticleItem[]=[];@StatesearchHistory:string[]=[];@StatehasSearched:boolean=false;
  • query:搜索框绑定值,双向数据流
  • filteredArticles:过滤后的文章列表(初始 = 全部文章)
  • searchHistory:搜索历史数组(通过不可变更新维护)
  • hasSearched:是否执行过搜索——用于区分"初始全部展示"和"搜索无结果"状态

3.3 实时过滤逻辑

搜索的核心逻辑在doSearch方法中:

doSearch(q:string):void{constlower=q.toLowerCase();constresult:ArticleItem[]=[];for(leti=0;i<this.allArticles.length;i++){consta=this.allArticles[i];if(a.title.toLowerCase().indexOf(lower)!==-1||a.summary.toLowerCase().indexOf(lower)!==-1||a.category.toLowerCase().indexOf(lower)!==-1){result.push(a);}}this.filteredArticles=result;this.resultCount=result.length;}

搜索范围覆盖三个字段:标题、摘要和分类。使用indexOf做子串匹配(而非精确匹配),确保"路由"能匹配到"导航路由"。搜索忽略大小写(toLowerCase),提升容错性。

onChangeonSubmit都调用doSearch,但onSubmit额外记录搜索历史:

onSearchChange(value:string):void{this.query=value;constq=value.trim();if(q===''){this.filteredArticles=this.allArticles;this.hasSearched=false;return;}this.hasSearched=true;this.doSearch(q);}onSearchSubmit(value:string):void{constq=value.trim();if(q==='')return;this.hasSearched=true;this.addToHistory(q);// 仅 onSubmit 记录历史this.doSearch(q);}

注意onChange中当搜索框清空时恢复到全部文章列表——这是重要的用户体验细节。用户删除所有文字后,应该看到完整的文章列表,而不是停留在上一次的搜索结果。

3.4 搜索历史管理

搜索历史使用不可变数组更新模式:

addToHistory(q:string):void{constnewHistory:string[]=[];// 去重:遍历旧历史,跳过相同词for(leti=0;i<this.searchHistory.length;i++){if(this.searchHistory[i]!==q){newHistory.push(this.searchHistory[i]);}}// 新词插到最前面newHistory.unshift(q);// 最多保留 8 条if(newHistory.length>8){newHistory.pop();}this.searchHistory=newHistory;}

这个逻辑实现了三个功能:

  1. 去重:如果搜索词已在历史中,先移除旧的,再添加新的(移到最前面)
  2. 排序:最近搜索的词在最前面(unshift插入到数组头部)
  3. 上限:最多保留 8 条(pop移除最早的历史)

为什么要限制 8 条?因为搜索历史用 Flex Wrap 标签展示,8 个标签约 2-3 行——如果无限制,历史标签会占据过多空间,挤压搜索结果区域。

点击历史标签复用搜索词:

tapHistory(q:string):void{this.query=q;// 填入搜索框(双向绑定更新 UI)this.hasSearched=true;this.doSearch(q);// 立即执行搜索}

清除所有历史:

clearHistory():void{this.searchHistory=[];}

点击清除后历史区域通过条件渲染(if (this.searchHistory.length > 0))直接消失。

3.5 三种视图状态

搜索页面有三个视图状态,通过条件渲染管理:

状态 1:初始状态(未搜索)

  • Search 搜索框显示
  • 搜索历史区域(如果有历史记录)
  • 全部文章列表(10 篇)

hasSearched === false && query === ''

状态 2:有结果状态

  • Search 搜索框显示(含输入文字)
  • 搜索历史消失(因为正在搜索中)
  • 结果计数:“找到 X 条结果”
  • 过滤后的文章列表

hasSearched === true && filteredArticles.length > 0

状态 3:无结果状态

  • Search 搜索框显示(含输入文字)
  • 搜索历史消失
  • 结果计数:“找到 0 条结果”
  • 空状态提示(😕 + “没有找到相关文章”)

hasSearched === true && filteredArticles.length === 0

三态切换通过条件渲染自然实现,不需要额外的状态机。关键是用hasSearched区分"还没搜过"和"搜过了但是没结果"——如果只靠filteredArticles.length === 0判断,初始状态(还没搜)也会被误判为无结果状态。

3.6 文章详情弹窗

点击文章卡片展示详情弹窗,包含分类标签、标题、日期和完整摘要:

showArticleDetail(article:ArticleItem):void{this.selectedArticle=article;this.showDetail=true;}

弹窗使用Stack自建而非系统 AlertDialog——可以展示任意布局(分类标签 + 标题 + 日期 + 正文)。半透明背景层点击可关闭弹窗。

3.7 页面结构

┌──────────────────────────────────────────┐ │ 🔍 本地搜索(深色标题栏) │ ├──────────────────────────────────────────┤ │ 📘 Search 组件说明卡片 │ ├──────────────────────────────────────────┤ │ 🔍 搜索文章标题、内容... [搜索] │ ← Search 组件 ├──────────────────────────────────────────┤ │ ┌────────────────────────────────────┐ │ │ │ 搜索历史 清除 │ │ │ │ [路由] [@State] [状态] [动画] │ │ ← 仅 query 为空时显示 │ └────────────────────────────────────┘ │ ├──────────────────────────────────────────┤ │ 找到 3 条结果 │ ├──────────────────────────────────────────┤ │ ┌────────────────────────────────────┐ │ │ │ [导航路由] 2026-04-25 │ │ │ │ 页面路由与导航设计 │ │ │ │ 使用router.pushUrl实现页面跳转... │ │ │ └────────────────────────────────────┘ │ │ ┌────────────────────────────────────┐ │ │ │ [导航路由] 2026-04-25 │ │ │ │ ...更多文章卡片... │ │ │ └────────────────────────────────────┘ │ └──────────────────────────────────────────┘

四、Search 组件的最佳实践

4.1 搜索范围的选择

搜索范围(搜索哪些字段)直接影响搜索的准确性和用户体验:

  • 范围太窄:用户用"布局"搜不到"ArkUI声明式UI范式详解"(虽然内容涉及布局,但标题没提到)→ 用户觉得"搜不到东西"
  • 范围太广:搜索"的"匹配到几乎所有文章 → 搜索结果失去意义

推荐搜索范围:标题 + 内容摘要 + 分类标签。标题是核心匹配项(权重最高),摘要提供补充匹配(覆盖更多关键词),分类标签匹配用于"按类别搜索"的场景(如搜索"动画"找到动画类文章)。

4.2 onChange vs onSubmit 的实战选择

在实际项目中,选择 onChange 还是 onSubmit 取决于搜索成本:

  • 本地数据(<1000 条):使用onChange。遍历过滤开销极小(毫秒级),用户享受即时反馈。
  • 本地数据(>1000 条):仍可使用onChange,但建议做防抖(debounce)——用户停止输入 300ms 后再执行过滤,避免每次按键都遍历大量数据。
  • 远程搜索:使用onSubmit。每次搜索都是一次网络请求,必须由用户主动触发。

本 Demo 使用onChange作为主搜索事件(10 篇文章,遍历耗时 <1ms),onSubmit用来保存搜索历史。这种"onChange 过滤 + onSubmit 记录"的模式是本地搜索的最佳实践。

4.3 搜索历史的用户体验

搜索历史的几个设计细节:

去重 + 前移:如果用户再次搜索同一个词,旧的记录应该移除,新的记录移到最前面——因为"最近搜索"比"首次搜索"更有参考价值。

数量上限:推荐 5-10 条。太少——用户可能找不到几天前的搜索;太多——占据过多屏幕空间。

仅记录有效搜索:只在onSubmit中记录历史,不在onChange中记录——用户输入到一半时删除文字不应产生历史记录。

一键清除:清除按钮应放在历史区域右上角,使用较为明显的颜色(如红/橙色)——这不是常用操作,但要容易找到。

4.4 空状态设计

空结果页面不是"报错",而是"引导":

  • 不要显示红色的"错误"信息——搜索无结果不是错误
  • 使用轻松的语气和图标:“😕 没有找到相关文章,试试其他关键词”
  • 给出建议:“尝试搜索:路由、状态、动画”(可选——列出几个热门关键词引导用户)

一个好的空状态不仅告知"没有结果",还帮助用户理解原因(“这个关键词不匹配"vs"没有任何文章”)。

4.5 搜索与键盘的配合

Search 组件在 HarmonyOS 中自动处理焦点和键盘弹出。需要关注的细节:

  • 首次进入页面:Search 不应自动获取焦点——用户可能要浏览历史或文章列表,自动弹出键盘会遮挡内容。
  • 点击搜索按钮后:键盘自动收起(Search 组件默认行为)。
  • 滚动列表时:键盘不应自动收起——用户可能在键盘可见时浏览结果。

如果需要在特定时机手动获取焦点,可以使用focusablefocusOnTouch属性控制。

五、完整代码结构

SearchPage (~220 行) ├── 数据定义 │ ├── ArticleItem 接口 — title / category / summary / date │ └── allArticles[10] — 10 篇内置文章 ├── 状态变量 │ ├── @State query — 搜索框文本 │ ├── @State filteredArticles — 过滤结果 │ ├── @State searchHistory — 搜索历史(最多 8 条) │ ├── @State hasSearched — 是否已搜索 │ └── @State showDetail / selectedArticle — 详情弹窗 ├── 业务逻辑 │ ├── doSearch() — 标题+摘要+分类匹配 │ ├── onSearchChange() — onChange 实时过滤 │ ├── onSearchSubmit() — onSubmit 过滤+记录历史 │ ├── addToHistory() — 去重+前移+限数量 │ ├── tapHistory() — 点击历史标签搜索 │ └── clearHistory() — 清空历史 ├── 视图 │ ├── 标题栏 — 🔍 本地搜索 │ ├── 说明卡片 — Search 组件介绍 │ ├── Search 组件 — 占位文字 + 搜索按钮 + onChange + onSubmit │ ├── 搜索历史区域(Flex Wrap 标签 + 清除按钮) │ ├── 结果计数("找到 X 条结果") + ForEach 文章列表 │ └── 空状态(😕 + "没有找到相关文章") └── 详情弹窗(Stack 叠加)

六、总结

本文通过一个本地知识库搜索Demo 深入讲解了 HarmonyOS NEXT 中的Search 搜索组件。Search 将传统的手动搜索栏布局封装为声明式组件,通过onChange(实时过滤)和onSubmit(确认搜索)两个事件覆盖本地和远程两种搜索场景。

核心要点回顾:

  1. Search 的双事件模式onChange用于实时本地过滤(filter-as-you-type),onSubmit用于确认搜索和保存历史。两者可以共存——onChange 负责过滤,onSubmit 负责记录。

  2. 搜索范围的权衡:标题 + 摘要 + 分类是最实用的搜索范围组合。标题提供精准匹配,摘要扩展覆盖,分类支持按类别浏览。

  3. 搜索历史的三个操作:去重(避免重复记录)、前移(最近搜索在前)、限数量(最多 8 条)。这些操作通过不可变数组模式实现。

  4. 三种视图状态的切换:初始态(全部文章 + 搜索历史)、有结果态(过滤列表 + 计数)、无结果态(空状态提示)。hasSearched状态变量区分"还没搜"和"搜了没结果"。

  5. Search 是"受控组件":通过value属性与@State绑定实现双向数据流——用户输入自动更新状态,外部修改状态(如点击历史标签)同步更新搜索框。

Search 组件是 HarmonyOS NEXT 中"实用优先"的典型代表——它的 API 只有 3 个参数 + 2 个事件,但覆盖了搜索交互的核心需求。不需要手动拼装 TextInput + Image + Button,不需要管理焦点和键盘逻辑——Search 让搜索功能回归到"声明事件 + 处理数据"这一本质。

七、扩展思考

Search 组件解决的是搜索框的 UI 和交互问题,但完整的搜索体验还包括以下方面:

搜索结果高亮:在结果列表中高亮显示匹配的关键词——例如搜索"路由",标题中的"路由"二字用蓝色标记。这需要 Text 组件的 Span 子组件实现分片渲染,将匹配文本与非匹配文本使用不同颜色。

搜索建议:在搜索框中输入时,下方展示搜索建议(自动补全)。这需要额外的建议词列表和下拉 UI。

搜索排序:当前过滤结果保持原始顺序。在实际项目中,可能需要按相关度排序——标题匹配权重 > 摘要匹配 > 分类匹配,甚至支持拼音搜索和模糊匹配。

远程搜索:当数据源是远程 API 时,需要管理加载状态(搜索中显示加载动画)、错误状态(网络错误提示)和防抖(用户停止输入 500ms 后再发请求)。

这些扩展说明:Search 组件是搜索体验的起点,它解决了搜索框本身的问题,而搜索的深度和精度取决于搜索逻辑层的设计。理解 Search 的核心机制(双向绑定、双事件模式、视图状态切换)是构建任何搜索功能的基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询