HarmonyOS6 半年磨一剑:RcTag 组件实战案例(三)技能标签墙与搜索热词

HarmonyOS6 半年磨一剑:RcTag 组件实战案例(三)技能标签墙与搜索热词 文章目录一、场景五用户技能标签墙1.1 场景描述1.2 设计思路1.3 完整代码1.4 代码详解二、场景六搜索热词标签云2.1 场景描述2.2 设计思路2.3 完整代码2.4 代码详解三、最佳实践总结3.1 属性选择策略3.2 性能注意事项3.3 多标签间距推荐四、常见问题与解答4.1 点击关闭按钮同时触发了 onRcTagClick4.2 rcTagShow 绑定的状态变量不响应4.3 rcTagPlainFill 没有效果4.4 ForEach 渲染标签时点击事件不更新五、属性速查表5.1 完整属性列表5.2 事件说明总结Hello 各位开发者们大家好我是若城本篇是RcTag实战系列的第三篇文章也是收官篇。本篇将讲解技能标签墙和搜索热词两个综合性较强的案例并在文末附上最佳实践总结、常见问题解答和完整属性速查表帮助开发者全面掌握RcTag的使用。一、场景五用户技能标签墙1.1 场景描述用户个人主页展示技能标签多种颜色和尺寸可编辑模式下支持删除通过加号按钮添加。这类场景常见于招聘 App 的候选人页面、开发者社区的个人主页、知识分享平台的作者简介等技能标签的颜色和大小差异往往用来表达技能类别或熟练程度。1.2 设计思路这个场景引入了浏览模式与编辑模式两种状态的切换。在浏览模式下标签仅展示没有关闭按钮切换到编辑模式后每个标签右侧出现关闭图标同时末尾追加一个 添加风格的虚线标签供用户点击添加新技能。这种模式在 iOS/Android 原生 App 中已经非常成熟RcTag的rcTagClosable属性和条件渲染特性使这套模式可以用极少的代码实现。多尺寸混排是这个案例的视觉亮点。核心技能如 HarmonyOS、ArkTS使用large尺寸中等熟练技能使用medium辅助技能使用mini。不同的尺寸建立了明显的视觉权重差异让浏览者一眼就能判断技能的重要程度这是比数字评级更直观的表达方式。1.3 完整代码import{RcTag}fromrchouiinterfaceSkillTag{id:stringname:stringtype:primary|success|warning|error|infosize:large|medium|mini}EntryComponentV2struct SkillTagWallDemo{LocalisEditMode:booleanfalseLocalskills:SkillTag[][{id:1,name:HarmonyOS,type:primary,size:large},{id:2,name:ArkTS,type:primary,size:large},{id:3,name:ArkUI,type:success,size:medium},{id:4,name:TypeScript,type:success,size:medium},{id:5,name:React,type:warning,size:medium},{id:6,name:Vue3,type:warning,size:medium},{id:7,name:Flutter,type:info,size:mini},{id:8,name:Kotlin,type:info,size:mini},{id:9,name:Swift,type:error,size:mini}]privateremoveSkill(id:string):void{constidxthis.skills.findIndex(ss.idid)if(idx0){this.skills.splice(idx,1)}}build(){Column({space:16}){// 标题栏Row(){Text(技能标签).fontSize(16).fontWeight(FontWeight.Medium).fontColor(#1a1a1a)Blank()Text(this.isEditMode?完成:编辑).fontSize(13).fontColor(#3c9cff).onClick((){this.isEditMode!this.isEditMode})}.width(100%)// 标签墙Flex({wrap:FlexWrap.Wrap}){ForEach(this.skills,(skill:SkillTag){RcTag({rcTagText:skill.name,rcTagType:skill.type,rcTagSize:skill.size,rcTagShape:circle,rcTagClosable:this.isEditMode,rcTagMargin:{right:8,bottom:8},onRcTagClose:(){this.removeSkill(skill.id)}})},(skill:SkillTag)skill.id)// 编辑模式下显示添加按钮风格的标签if(this.isEditMode){RcTag({rcTagText: 添加,rcTagType:primary,rcTagShape:circle,rcTagPlain:true,rcTagSize:medium,rcTagMargin:{right:8,bottom:8},onRcTagClick:(){// 实际项目中打开添加弹窗}})}}.width(100%)// 统计Text(共${this.skills.length}项技能).fontSize(12).fontColor(#909399)}.padding(16).width(100%).backgroundColor(Color.White).borderRadius(12).margin(16)}}1.4 代码详解SkillTag 接口设计SkillTag接口定义了四个字段id用字符串存储作为 ForEach 的唯一 keyname是显示的技能名称type的类型与RcTag的rcTagType完全匹配可以直接透传size同样与rcTagSize匹配。这种让数据结构与 UI 组件 Props 对齐的设计能最大限度地消除数据到 UI 的映射转换代码数据字段可以几乎零转换地传给组件。两种模式的状态驱动isEditMode是驱动双模式切换的唯一状态变量。当它为false时rcTagClosable: this.isEditMode等于rcTagClosable: false所有标签不显示关闭图标为true时所有标签出现关闭图标同时if (this.isEditMode)分支渲染 添加标签。标题栏右侧的切换文字通过三元表达式this.isEditMode ? 完成 : 编辑动态展示点击后执行this.isEditMode !this.isEditMode取反切换模式。整套双模式逻辑只用了一个布尔变量代码非常精简。removeSkill 与 id 而非 name 的删除removeSkill接收的是skill.id而非skill.name。使用 ID 删除的原因是同名技能如两个人都标记了Java在同一个列表中虽然不常见但理论上存在若用名称删除可能误删。用 ID 则精确定位不会有歧义。方法内使用findIndex找到对应 ID 的索引然后splice(idx, 1)删除该项。由于skills是Local状态变量原地修改后 ArkTS 响应式系统会检测到变化自动触发ForEach的差量更新。ForEach 的 key 选择 id 字段标签墙的ForEach使用(skill: SkillTag) skill.id作为 key 函数返回的是字符串型 ID。用 ID 而非skill.name作为 key 的优势在于当用户删除某个技能时ArkTS 框架通过 key 能精确判断哪条数据被移除只卸载那一个标签节点其他标签节点保持不变。若用名称作 key在用户快速删除多个标签时可能因 key 对比不稳定导致不必要的重渲染。 添加标签的设计“ 添加标签使用rcTagPlain: true使其呈现为镂空描边样式与实色填充的技能标签形成明显区分用户能直觉感知这个是按钮不是标签”。rcTagShape: circle与其他技能标签保持一致视觉风格统一。onRcTagClick回调中留有注释提示实际项目中打开添加弹窗说明这里只是展示交互入口真实项目中会弹出输入弹窗或跳转到选择页面。二、场景六搜索热词标签云2.1 场景描述搜索页面展示热词与历史记录标签云点击标签直接填入搜索框支持清空历史。这是搜索功能的标配体验几乎所有内容型 App 都有这个页面。搜索历史帮助用户快速回到上一次搜索热门搜索则起到内容引导和发现的作用两块区域的视觉处理也有所不同。2.2 设计思路历史标签与热词标签在视觉上做了明显区分。历史标签使用rcTagType: inforcTagPlain: true的灰色镂空样式低调不抢眼体现历史的次要属性。热词标签则将前三名设计为rcTagType: error红色实色其余使用rcTagType: primaryrcTagPlain: true蓝色镂空前三名的视觉强调对应其在热度榜上的头部地位这是内容平台常用的排行视觉设计。2.3 完整代码import{RcTag}fromrchouiEntryComponentV2struct SearchHotTagDemo{LocalsearchText:stringLocalhistoryTags:string[][HarmonyOS,ArkTS 教程,鸿蒙组件库,rchoui]privatehotTags:string[][HarmonyOS NEXT,ArkUI 布局,鸿蒙开发入门,ComponentV2,AppStorageV2,状态管理,自定义组件,生命周期]build(){Column({space:0}){// 搜索框Row({space:8}){TextInput({placeholder:搜索...,text:this.searchText}).fontSize(14).height(38).layoutWeight(1).borderRadius(19).backgroundColor(#f5f5f5).padding({left:16,right:16}).onChange((val){this.searchTextval})Text(搜索).fontSize(14).fontColor(#3c9cff).padding({left:4})}.padding({left:16,right:16,top:12,bottom:12}).backgroundColor(Color.White)Scroll(){Column({space:20}){// 搜索历史if(this.historyTags.length0){Column({space:10}){Row(){Text(搜索历史).fontSize(14).fontWeight(FontWeight.Medium).fontColor(#1a1a1a)Blank()Text(清空).fontSize(12).fontColor(#909399).onClick((){this.historyTags[]})}.width(100%)Flex({wrap:FlexWrap.Wrap}){ForEach(this.historyTags,(tag:string){RcTag({rcTagText:tag,rcTagType:info,rcTagPlain:true,rcTagShape:circle,rcTagMargin:{right:8,bottom:8},onRcTagClick:(){this.searchTexttag}})},(tag:string)tag)}}.alignItems(HorizontalAlign.Start).width(100%)}// 热门搜索Column({space:10}){Text(热门搜索).fontSize(14).fontWeight(FontWeight.Medium).fontColor(#1a1a1a).width(100%)Flex({wrap:FlexWrap.Wrap}){ForEach(this.hotTags,(tag:string,idx:number){RcTag({rcTagText:tag,// 前三名用特殊颜色突出rcTagType:idx3?error:primary,rcTagPlain:idx3,rcTagShape:circle,rcTagMargin:{right:8,bottom:8},onRcTagClick:(){this.searchTexttag// 加入历史if(!this.historyTags.includes(tag)){this.historyTags.unshift(tag)if(this.historyTags.length10){this.historyTags.pop()}}}})},(tag:string)tag)}}.alignItems(HorizontalAlign.Start).width(100%)}.padding(16).width(100%)}.layoutWeight(1).backgroundColor(#f5f5f5)}.width(100%).height(100%)}}2.4 代码详解状态变量分工searchText存储搜索框当前的文字用Local装饰以响应式更新输入框显示。historyTags存储搜索历史数组也是Local对它的增删清空都能触发历史区域的重渲染。hotTags是固定的热词数据内容不会变化因此用普通private存储不需要响应式。搜索框的设计TextInput的borderRadius(19)配合height(38)使输入框呈现胶囊形状这是当前移动端搜索框的主流设计语言。backgroundColor(#f5f5f5)给输入框一个浅灰色背景与白色页面区分使输入框有视觉边界感。旁边的搜索文字用Text而非Button实现这是移动端搜索框的常见做法视觉上更轻量虽然本示例中搜索文字没有绑定实际的搜索逻辑但留下了扩展入口。历史区域的条件渲染整个历史区域被包裹在if (this.historyTags.length 0)条件中。当历史记录被清空后historyTags长度变为 0历史区域整体从 DOM 树中移除不留白布局自动收缩热词区域上移填充空间。这比用visibility隐藏或用height(0)折叠要干净。清空历史的赋值方式this.historyTags []将状态变量重新赋值为一个空数组。与splice(0, historyTags.length)等原地清空方式相比直接赋新数组更简洁且在 ArkTS 的响应式系统中同样能被检测到变化并触发重渲染。历史标签的点击回调历史标签的onRcTagClick: () { this.searchText tag }直接将标签文字赋给searchText使搜索框填入该关键词。这里的tag来自ForEach的回调闭包在闭包创建时已经捕获了当前迭代的标签文字所以无论点击哪个标签都能正确填入对应内容。热词标签的索引驱动样式热词的 ForEach 回调有两个参数(tag: string, idx: number)idx是当前元素的索引从 0 开始。rcTagType: idx 3 ? error : primary和rcTagPlain: idx 3这两行通过索引完成了视觉分层索引 0、1、2前三名使用红色实色索引 3 及之后使用蓝色镂空。注意rcTagPlain和rcTagType之间的联动是有意设计的前三名同时满足rcTagType: error且rcTagPlain: false后几名同时满足rcTagType: primary且rcTagPlain: true两者配合才能产生正确的视觉效果。热词点击时的历史管理逻辑点击热词时执行了三步操作首先将搜索框填入该关键词然后检查该词是否已在历史中若不在则调用unshift插入到历史数组头部最近搜索排最前最后检查历史数量是否超过 10 条超出时调用pop移除末尾最旧的记录。这三步合起来实现了一个带上限的最近搜索队列是搜索历史功能的标准实现模式。Scroll 的 layoutWeight搜索框位于顶部白色区域内容区历史热词位于Scroll中。Scroll设置了layoutWeight(1)使其在父容器Column中占据搜索框以外的所有剩余高度内容溢出时可以上下滑动。backgroundColor(#f5f5f5)给内容区一个浅灰背景与搜索框区域的白色形成分区视觉层次清晰。三、最佳实践总结3.1 属性选择策略在实际项目中RcTag 的属性组合有一些规律性的用法。强调类状态标识如待支付、“紧急等应使用实色 medium 尺寸确保用户第一时间注意到。次要状态标识如运输中”、“处理中”可以降级为镂空填充 mini在保留语义的同时减少视觉干扰。筛选类标签建议统一使用胶囊形状circle符合移动端筛选条的视觉惯例且选中时用实色、未选中时用镂空的切换方式最为直观。内容分类标签数量通常较多推荐使用镂空填充 square mini信息密度高但不杂乱。可删除的用户输入标签则适合使用实色 circle closable实色提升存在感胶囊形状更友好关闭图标明确传达可操作性。3.2 性能注意事项在build()函数中多次调用同一个计算方法如订单状态案例中多次调用getStatusConfig会在每次渲染时产生重复计算。对于配置类查询建议在ListItem的渲染逻辑开始处声明局部变量缓存结果后续复用局部变量。ForEach的 key 函数选择对差量更新性能影响显著。应当优先使用数据中的唯一 ID 字段作为 key避免使用数组索引因为删除操作会导致索引移位进而引发不必要的节点重建。rcTagShow与条件渲染if适用场景不同。rcTagShow适合频繁切换显隐的标签因为它不销毁节点只控制可见性条件渲染适合永久消失的标签如从数组中删除直接移除 DOM 节点更彻底。3.3 多标签间距推荐在使用FlexFlexWrap.Wrap进行多标签自动换行布局时推荐统一使用rcTagMargin: { right: 8, bottom: 8 }的组合。右边距控制同行标签间的水平间距下边距控制换行后的行间距这样无论标签在哪一行哪一位置它与右侧和下方邻近元素的距离都是一致的整体间距均匀、视觉整洁。在标签数量固定且不换行的情况下如Row内排列少量标签也可以直接用Row({ space: 8 })统一控制间距无需借助rcTagMargin。四、常见问题与解答4.1 点击关闭按钮同时触发了 onRcTagClick这种情况通常是误解导致的。RcTag 内部的关闭按钮点击处理已经调用了event.stopPropagation()阻止了事件冒泡因此正常情况下点击关闭图标不会触发外层标签的onRcTagClick。如果确实遇到了两个回调同时触发的情况需要排查外层容器是否有额外的点击事件监听器或者检查rcTagClosable是否正确设置为true。4.2 rcTagShow 绑定的状态变量不响应原因是状态变量没有使用响应式装饰器声明。在 ComponentV2 中只有用Local、Param等装饰器修饰的变量发生变化时才会触发组件重新渲染。普通的private变量即使值变了UI 也不会更新。// 错误普通变量不触发响应式更新privateshow:booleantrue// 正确使用 Local 装饰Localshow:booleantrue4.3 rcTagPlainFill 没有效果rcTagPlainFill必须与rcTagPlain: true同时使用才能生效。rcTagPlainFill的作用是在镂空模式基础上增加浅色背景填充若没有开启rcTagPlain标签处于实色模式rcTagPlainFill无法产生任何额外效果。// 错误缺少 rcTagPlainRcTag({rcTagText:无效,rcTagPlainFill:true})// 正确两者同时设置RcTag({rcTagText:有效,rcTagPlain:true,rcTagPlainFill:true})4.4 ForEach 渲染标签时点击事件不更新当在ForEach中使用索引idx判断选中状态时若idx没有通过响应式路径传递点击后 UI 不会更新。正确的做法是通过rcTagName将标识符携带到事件回调中然后修改Local状态变量由状态变化驱动 UI 更新。// 正确做法通过 rcTagName 携带标识在事件中更新状态ForEach(this.options,(opt:string,idx:number){RcTag({rcTagText:opt,rcTagName:idx,rcTagPlain:this.selected!idx,onRcTagClick:(name){this.selectednameasnumber// 修改 Local 变量触发更新}})},(opt:string)opt)五、属性速查表5.1 完整属性列表属性类型默认值说明rcTagTextstring | number标签文字rcTagTypeprimary | success | warning | error | infoprimary主题颜色rcTagSizelarge | medium | minimedium尺寸rcTagShapesquare | circlesquare方形或胶囊形rcTagPlainbooleanfalse镂空描边模式rcTagPlainFillbooleanfalse镂空浅色填充需配合 rcTagPlain 使用rcTagDisabledbooleanfalse禁用状态禁用后不响应点击和关闭rcTagClosablebooleanfalse是否显示关闭图标rcTagShowbooleantrue标签是否可见rcTagIconstring | ResourceStr-左侧图标资源rcTagBgColorResourceColor-自定义背景色覆盖主题色rcTagColorResourceColor-自定义文字色覆盖主题色rcTagBorderColorResourceColor-自定义边框色覆盖主题色rcTagCloseColorResourceColor-自定义关闭图标颜色rcTagNamestring | number-标签标识符在事件回调中透传rcTagMarginPadding | Length-外边距用于控制标签与周围元素的距离rcTagPaddingPadding | Length-内边距覆盖默认内边距onRcTagClickFunction-标签点击回调参数为 rcTagName 的值onRcTagCloseFunction-关闭图标点击回调参数为 rcTagName 的值5.2 事件说明onRcTagClick在用户点击标签时触发禁用状态下不触发回调参数为rcTagName的值。若未设置rcTagName回调参数为undefined。onRcTagClose在用户点击关闭图标时触发需同时设置rcTagClosable: true禁用状态下不触发回调参数同样为rcTagName的值。关闭图标的点击事件不会冒泡到onRcTagClick两者相互独立。总结至此RcTag实战系列三篇文章通过六个真实业务场景系统演示了RcTag在鸿蒙 App 开发中的完整应用范围从纯展示的内容标签到单选筛选的交互标签从可增删的标签管理器到配置表驱动的订单状态从多尺寸混排的技能墙到索引驱动差异化样式的搜索热词。RcTag以极简的 API 设计覆盖了从简单到复杂的全场景需求这正是半年磨一剑所沉淀的工程价值——用简洁的设计解决复杂的问题。如果这篇文章对你有帮助欢迎点赞、收藏、关注你的支持是我持续创作的动力