鸿蒙新特性——Search 搜索组件详解
一、引言
搜索是移动端应用最基础的功能入口之一。从 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 })
| 参数 | 类型 | 说明 |
|---|---|---|
placeholder |
string |
占位提示文字。当搜索框为空时显示,引导用户输入 |
value |
string |
搜索框的当前文本值。与 @State 绑定可实现双向数据流 |
icon |
string |
搜索图标。默认为系统搜索图标,可传入 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 数据结构
interface ArticleItem {
title: string; // 标题
category: string; // 分类
summary: string; // 摘要
date: string; // 日期
}
@State query: string = '';
@State filteredArticles: ArticleItem[] = [];
@State searchHistory: string[] = [];
@State hasSearched: boolean = false;
query:搜索框绑定值,双向数据流filteredArticles:过滤后的文章列表(初始 = 全部文章)searchHistory:搜索历史数组(通过不可变更新维护)hasSearched:是否执行过搜索——用于区分"初始全部展示"和"搜索无结果"状态
3.3 实时过滤逻辑
搜索的核心逻辑在 doSearch 方法中:
doSearch(q: string): void {
const lower = q.toLowerCase();
const result: ArticleItem[] = [];
for (let i = 0; i < this.allArticles.length; i++) {
const a = 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),提升容错性。
onChange 和 onSubmit 都调用 doSearch,但 onSubmit 额外记录搜索历史:
onSearchChange(value: string): void {
this.query = value;
const q = value.trim();
if (q === '') {
this.filteredArticles = this.allArticles;
this.hasSearched = false;
return;
}
this.hasSearched = true;
this.doSearch(q);
}
onSearchSubmit(value: string): void {
const q = value.trim();
if (q === '') return;
this.hasSearched = true;
this.addToHistory(q); // 仅 onSubmit 记录历史
this.doSearch(q);
}
注意 onChange 中当搜索框清空时恢复到全部文章列表——这是重要的用户体验细节。用户删除所有文字后,应该看到完整的文章列表,而不是停留在上一次的搜索结果。
3.4 搜索历史管理
搜索历史使用不可变数组更新模式:
addToHistory(q: string): void {
const newHistory: string[] = [];
// 去重:遍历旧历史,跳过相同词
for (let i = 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;
}
这个逻辑实现了三个功能:
- 去重:如果搜索词已在历史中,先移除旧的,再添加新的(移到最前面)
- 排序:最近搜索的词在最前面(
unshift插入到数组头部) - 上限:最多保留 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 组件默认行为)。
- 滚动列表时:键盘不应自动收起——用户可能在键盘可见时浏览结果。
如果需要在特定时机手动获取焦点,可以使用 focusable 和 focusOnTouch 属性控制。
五、完整代码结构
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(确认搜索)两个事件覆盖本地和远程两种搜索场景。
核心要点回顾:
-
Search 的双事件模式:
onChange用于实时本地过滤(filter-as-you-type),onSubmit用于确认搜索和保存历史。两者可以共存——onChange 负责过滤,onSubmit 负责记录。 -
搜索范围的权衡:标题 + 摘要 + 分类是最实用的搜索范围组合。标题提供精准匹配,摘要扩展覆盖,分类支持按类别浏览。
-
搜索历史的三个操作:去重(避免重复记录)、前移(最近搜索在前)、限数量(最多 8 条)。这些操作通过不可变数组模式实现。
-
三种视图状态的切换:初始态(全部文章 + 搜索历史)、有结果态(过滤列表 + 计数)、无结果态(空状态提示)。
hasSearched状态变量区分"还没搜"和"搜了没结果"。 -
Search 是"受控组件":通过
value属性与@State绑定实现双向数据流——用户输入自动更新状态,外部修改状态(如点击历史标签)同步更新搜索框。
Search 组件是 HarmonyOS NEXT 中"实用优先"的典型代表——它的 API 只有 3 个参数 + 2 个事件,但覆盖了搜索交互的核心需求。不需要手动拼装 TextInput + Image + Button,不需要管理焦点和键盘逻辑——Search 让搜索功能回归到"声明事件 + 处理数据"这一本质。
七、扩展思考
Search 组件解决的是搜索框的 UI 和交互问题,但完整的搜索体验还包括以下方面:
搜索结果高亮:在结果列表中高亮显示匹配的关键词——例如搜索"路由",标题中的"路由"二字用蓝色标记。这需要 Text 组件的 Span 子组件实现分片渲染,将匹配文本与非匹配文本使用不同颜色。
搜索建议:在搜索框中输入时,下方展示搜索建议(自动补全)。这需要额外的建议词列表和下拉 UI。
搜索排序:当前过滤结果保持原始顺序。在实际项目中,可能需要按相关度排序——标题匹配权重 > 摘要匹配 > 分类匹配,甚至支持拼音搜索和模糊匹配。
远程搜索:当数据源是远程 API 时,需要管理加载状态(搜索中显示加载动画)、错误状态(网络错误提示)和防抖(用户停止输入 500ms 后再发请求)。
这些扩展说明:Search 组件是搜索体验的起点,它解决了搜索框本身的问题,而搜索的深度和精度取决于搜索逻辑层的设计。理解 Search 的核心机制(双向绑定、双事件模式、视图状态切换)是构建任何搜索功能的基础。
更多推荐




所有评论(0)