HarmonyOS6半年磨一剑 - RcIcon组件使用最佳实践与性能优化
文章目录
前言
各位开发者,大家好!我是若城。
在鸿蒙应用开发过程中,我发现许多组件样式和工具方法具有高度的复用性,但每次新项目都需要重复编写,这极大地降低了开发效率。因此,我决定投入半年时间,打造一款专为鸿蒙生态设计的 UI 组件库 —— rchoui。
项目简介
rchoui 是一个面向 HarmonyOS6 的企业级 UI 组件库,旨在提供开箱即用的高质量组件,让开发者告别"重复造轮子"。
核心特性
- 丰富组件:涵盖基础组件、表单组件、弹窗组件、布局组件等
- 设计规范:遵循统一的色彩体系和设计语言
- 工具集成:内置常用工具方法,提升开发效率
- 完善文档:每个模块都配有详细的设计思路和使用说明
开源计划
项目预计于 2026 年 7 月中旬正式开源,届时可通过三方库直接下载使用。在此期间,我会通过系列文章逐一介绍每个模块的设计思路与实现细节。
rchoui官网
目前暂定 rchoui 官网地址:http://rchoui.ruocheng.site/
需要注意的是 当前官网还在完善当中, 会在后续更新中逐步完善。届时可以为大家提供更加完善的说明文档

一、实战应用案例
1.1 底部导航栏
import{ RcIcon, IconName }from"rchoui"/** * Tab 项配置接口 */interfaceTabItem{/** Tab 名称 */ name:string/** 激活状态图标 */ icon:string/** 未激活状态图标(线型) */ iconOutline:string}@Entry@Component struct RcIconTab {@State activeIndex:number=0private tabItems: TabItem[]=[{ name:'首页', icon: IconName.HOME, iconOutline: IconName.HOME_OUTLINE},{ name:'发现', icon: IconName.COMPASS, iconOutline: IconName.COMPASS_OUTLINE},{ name:'消息', icon: IconName.MESSAGE_CIRCLE, iconOutline: IconName.MESSAGE_CIRCLE_OUTLINE},{ name:'我的', icon: IconName.PERSON, iconOutline: IconName.PERSON_OUTLINE}]build(){Row(){ForEach(this.tabItems,(item:TabItem, index)=>{Column({ space:4}){RcIcon({ name:this.activeIndex === index ? item.icon : item.iconOutline, iconSize:24, color:this.activeIndex === index ?'#1890ff':'#666666',onIconClick:()=>{this.activeIndex = index }})Text(item.name).fontSize(12).fontColor(this.activeIndex === index ?'#1890ff':'#666666')}.layoutWeight(1).padding(8)})}.width('100%').height(56).backgroundColor('#ffffff')}}
实现要点:
- 使用风格切换表达激活状态
- 颜色与风格同步变化
- 统一的视觉反馈
1.2 工具栏按钮组
interfaceToolsItem{ id:string name:string}@Component struct ToolbarExample {@State selectedTool:string=''private tools:ToolsItem[]=[{ id:'edit', name: IconName.EDIT},{ id:'copy', name: IconName.COPY},{ id:'trash', name: IconName.TRASH},{ id:'share', name: IconName.SHARE}]build(){Row({ space:16}){ForEach(this.tools,(tool:ToolsItem)=>{RcIcon({ name: tool.name, iconSize:20, color:this.selectedTool === tool.id ?'#1890ff':'#666666',onIconClick:()=>{this.selectedTool = tool.id this.handleToolClick(tool.id)}})})}.padding(12).backgroundColor('#f5f5f5').borderRadius(8)}handleToolClick(toolId:string){console.log(`工具 ${toolId} 被点击`)}}

1.3 列表项装饰
interfaceListItem{ id:string title:string icon:string status:'success'|'warning'|'error'|'info'}@Component struct ListExample {private items: ListItem[]=[{ id:'1', title:'任务已完成', icon: IconName.CHECKMARK_CIRCLE, status:'success'},{ id:'2', title:'待处理事项', icon: IconName.ALERT_CIRCLE, status:'warning'},{ id:'3', title:'操作失败', icon: IconName.CLOSE_CIRCLE, status:'error'},{ id:'4', title:'通知消息', icon: IconName.INFO, status:'info'}]build(){List({ space:12}){ForEach(this.items,(item: ListItem)=>{ListItem(){Row({ space:12}){RcIcon({ name: item.icon, iconSize:20, color:this.getStatusColor(item.status)})Text(item.title).fontSize(16).layoutWeight(1)RcIcon({ name: IconName.CHEVRON_RIGHT, iconSize:16, color:'#cccccc'})}.width('100%').padding(16).backgroundColor('#ffffff').borderRadius(8)}})}.padding(16)}getStatusColor(status:string): ResourceColor {const colorMap ={'success':'#52c41a','warning':'#faad14','error':'#ff4d4f','info':'#1890ff'}return colorMap[status]||'#666666'}}
1.4 输入框前后缀
@Component struct InputExample {@State searchText:string=''@State showClear:boolean=falsebuild(){Row(){// 前缀符号RcIcon({ name: IconName.SEARCH, iconSize:18, color:'#999999'}).margin({ left:12})// 输入框TextInput({ placeholder:'搜索内容', text:this.searchText }).layoutWeight(1).backgroundColor(Color.Transparent).padding({ left:8, right:8}).onChange((value:string)=>{this.searchText = value this.showClear = value.length >0})// 后缀清除按钮if(this.showClear){RcIcon({ name: IconName.CLOSE_CIRCLE, iconSize:16, color:'#cccccc',onIconClick:()=>{this.searchText =''this.showClear =false}}).margin({ right:12})}}.width('100%').height(40).backgroundColor('#f5f5f5').borderRadius(20)}}1.5 加载状态指示
@Component struct LoadingExample {@State isLoading:boolean=false@State rotationAngle:number=0build(){Column({ space:16}){// 旋转加载动画if(this.isLoading){RcIcon({ name: IconName.LOADER_OUTLINE, iconSize:32, color:'#1890ff', iconAnimation:{ duration:1000, curve: Curve.Linear, iterations:-1}}).rotate({ angle:this.rotationAngle }).onAppear(()=>{this.rotationAngle =360})Text('加载中...').fontSize(14).fontColor('#666666')}else{RcIcon({ name: IconName.CHECKMARK_CIRCLE, iconSize:32, color:'#52c41a'})Text('加载完成').fontSize(14).fontColor('#52c41a')}Button(this.isLoading ?'停止':'开始加载').onClick(()=>{this.isLoading =!this.isLoading if(!this.isLoading){this.rotationAngle =0}})}}}1.6 空状态页面
@Component struct EmptyStateExample {@Prop emptyType:'noData'|'noNetwork'|'error'='noData'build(){Column({ space:20}){RcIcon({ name:this.getEmptyIcon(), iconSize:80, color:'#cccccc'})Text(this.getEmptyText()).fontSize(16).fontColor('#666666')if(this.emptyType ==='error'){Button('重新加载').onClick(()=>{// 重新加载逻辑})}}.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('#f5f5f5')}getEmptyIcon():string{const iconMap ={'noData': IconName.FILE_OUTLINE,'noNetwork': IconName.WIFI_OFF_OUTLINE,'error': IconName.ALERT_TRIANGLE_OUTLINE}return iconMap[this.emptyType]}getEmptyText():string{const textMap ={'noData':'暂无数据','noNetwork':'网络连接失败','error':'加载失败'}return textMap[this.emptyType]}}1.7 评分组件
@Component struct RatingExample {@State rating:number=0private maxRating:number=5build(){Row({ space:8}){ForEach(Array.from({ length:this.maxRating }),(_, index)=>{RcIcon({ name: index <this.rating ? IconName.STAR: IconName.STAR_OUTLINE, iconSize:24, color: index <this.rating ?'#faad14':'#d9d9d9',onIconClick:()=>{this.rating = index +1}})})}}}1.8 标签页组件
@Component struct TabsExample {@State activeTab:number=0private tabs =[{ title:'推荐', icon: IconName.HOME},{ title:'关注', icon: IconName.HEART},{ title:'热门', icon: IconName.FLASH}]build(){Column(){// 标签页头部Row(){ForEach(this.tabs,(tab, index)=>{Column({ space:4}){RcIcon({ name: tab.icon, iconSize:20, color:this.activeTab === index ?'#1890ff':'#666666'})Text(tab.title).fontSize(14).fontColor(this.activeTab === index ?'#1890ff':'#666666')}.layoutWeight(1).padding(12).onClick(()=>{this.activeTab = index })})}.width('100%').backgroundColor('#ffffff')// 标签页内容区if(this.activeTab ===0){Text('推荐内容')}elseif(this.activeTab ===1){Text('关注内容')}else{Text('热门内容')}}}}二、性能优化实践
2.1 避免过度渲染
❌ 不推荐:每次都重新创建
@Component struct BadExample {@State count:number=0build(){Column(){// 问题:每次count变化,所有符号都重新渲染RcIcon({ name: IconName.HOME, iconSize:20})RcIcon({ name: IconName.SEARCH, iconSize:20})RcIcon({ name: IconName.PERSON, iconSize:20})Text(this.count.toString())}}}✅ 推荐:使用@Reusable优化
@Reusable@Component struct IconItem {@Param iconName:string=''build(){RcIcon({ name:this.iconName, iconSize:20})}}@Component struct GoodExample {@State count:number=0build(){Column(){IconItem({ iconName: IconName.HOME})IconItem({ iconName: IconName.SEARCH})IconItem({ iconName: IconName.PERSON})Text(this.count.toString())}}}2.2 列表性能优化
使用LazyForEach优化大列表
classBasicDataSourceimplementsIDataSource{private listeners: DataChangeListener[]=[]private data:string[]=[]getData(index:number):string{returnthis.data[index]}totalCount():number{returnthis.data.length }registerDataChangeListener(listener: DataChangeListener):void{this.listeners.push(listener)}unregisterDataChangeListener(listener: DataChangeListener):void{const index =this.listeners.indexOf(listener)if(index >=0){this.listeners.splice(index,1)}}}@Component struct OptimizedListExample {private dataSource: BasicDataSource =newBasicDataSource()build(){List(){LazyForEach(this.dataSource,(item:string, index:number)=>{ListItem(){Row({ space:12}){RcIcon({ name: IconName.FILE, iconSize:20, color:'#666666'})Text(item).fontSize(16)}.padding(16)}},(item:string)=> item)}}}2.3 动画性能优化
控制动画数量
@Component struct AnimationExample {@State visibleIcons:number=5private maxIcons:number=100build(){Column(){// 只为可见的符号添加动画ForEach(Array.from({ length: Math.min(this.visibleIcons,this.maxIcons)}),(_, index)=>{RcIcon({ name: IconName.STAR, iconSize:20, iconAnimation:{ duration:300, delay: index *50// 错开动画时间}})})}}}使用动画开关
@StorageLink('enableAnimations') enableAnimations:boolean=trueRcIcon({ name: IconName.LOADER_OUTLINE, iconSize:24, iconAnimation:this.enableAnimations ?{ duration:1000, iterations:-1}:undefined})2.4 资源加载优化
延迟加载非关键符号
@Component struct LazyLoadExample {@State showDetails:boolean=falsebuild(){Column({ space:12}){// 始终显示的关键符号RcIcon({ name: IconName.HOME, iconSize:24})Button('显示详情').onClick(()=>{this.showDetails =true})// 延迟加载的符号if(this.showDetails){Row({ space:8}){RcIcon({ name: IconName.EDIT, iconSize:20})RcIcon({ name: IconName.TRASH, iconSize:20})RcIcon({ name: IconName.SHARE, iconSize:20})}}}}}2.5 内存优化
及时清理资源
@Component struct MemoryOptimizedExample {@State rotationAngle:number=0private animationTimer?:numberaboutToAppear(){this.startAnimation()}aboutToDisappear(){// 清理定时器if(this.animationTimer){clearInterval(this.animationTimer)this.animationTimer =undefined}}startAnimation(){this.animationTimer =setInterval(()=>{this.rotationAngle =(this.rotationAngle +10)%360},50)}build(){RcIcon({ name: IconName.SYNC, iconSize:30}).rotate({ angle:this.rotationAngle })}}三、可访问性支持
3.1 语义化标签
RcIcon({ name: IconName.CLOSE}).accessibilityText('关闭按钮').accessibilityDescription('点击关闭当前窗口')3.2 焦点管理
@State isFocused:boolean=falseRcIcon({ name: IconName.BUTTON, color:this.isFocused ?'#0066cc':'#0080ff'}).focusable(true).onFocus(()=>{this.isFocused =true}).onBlur(()=>{this.isFocused =false})3.3 触摸区域扩展
// 符号本身较小时,扩大触摸区域Stack(){RcIcon({ name: IconName.SMALL, iconSize:16})}.width(44)// 最小44x44的触摸区域.height(44).onClick(()=>{// 点击处理})四、跨平台适配
4.1 屏幕密度适配
@State screenDensity:number= display.getDefaultDisplaySync().densityDPI /160RcIcon({ name: IconName.LOGO, iconSize:24*this.screenDensity // 根据密度缩放})4.2 暗黑模式适配
@StorageLink('colorMode') colorMode: ThemeColorMode = ThemeColorMode.LIGHTRcIcon({ name: IconName.THEME, color:this.colorMode === ThemeColorMode.DARK?'#ffffff':'#000000'})4.3 多语言支持
// 对于方向性符号,考虑RTL布局@StorageLink('isRTL') isRTL:boolean=falseRcIcon({ name:this.isRTL ? IconName.ARROW_LEFT: IconName.ARROW_RIGHT, iconSize:20})五、调试技巧
5.1 符号渲染检查
// 开发模式下显示符号名称@State showDebugInfo:boolean= BuildProfile.DEBUGRcIcon({ name: IconName.TEST}).border({ width:this.showDebugInfo ?1:0, color:'#ff0000'})if(this.showDebugInfo){Text('IconName.TEST').fontSize(10).fontColor('#ff0000')}5.2 性能监控
@Component struct PerformanceMonitor {@State renderCount:number=0aboutToAppear(){this.renderCount++console.log(`组件渲染次数: ${this.renderCount}`)}build(){RcIcon({ name: IconName.MONITOR})}}六、常见错误与解决方案
6.1 符号不显示
问题: 符号名称拼写错误
// ❌ 错误:拼写错误RcIcon({ name:'icon-houi_hom'})// ✅ 正确:使用常量避免拼写错误RcIcon({ name: IconName.HOME})问题: 字体未加载
// 确保字体文件存在于rawfile目录// 路径:entry/src/main/resources/rawfile/rc_font.woff6.2 颜色不生效
问题: 图片格式不支持颜色填充
// ❌ PNG图片不支持fillColorRcIcon({ name:'https://example.com/logo.png', color:'#ff0000'// 无效})// ✅ 使用SVG或字体符号RcIcon({ name: IconName.LOGO, color:'#ff0000'// 有效})6.3 动画卡顿
问题: 同时运行过多动画
// ❌ 100个符号同时动画ForEach(Array.from({ length:100}),()=>{RcIcon({ name: IconName.STAR, iconAnimation:{...}})})// ✅ 限制同时动画数量ForEach(Array.from({ length:100}),(_, index)=>{RcIcon({ name: IconName.STAR, iconAnimation: index <10?{...}:undefined})})6.4 内存泄漏
问题: 动画未清理
// ❌ 忘记清理定时器privatestartRotation(){setInterval(()=>{this.angle +=10},50)}// ✅ 在生命周期中清理private timer?:numberaboutToAppear(){this.timer =setInterval(()=>{this.angle +=10},50)}aboutToDisappear(){if(this.timer){clearInterval(this.timer)}}七、版本兼容性
7.1 API版本检查
import{ systemCapability }from'@ohos.ability.ability'if(systemCapability.querySystemCapability('SystemCapability.ArkUI.ArkUI.Full')){// 使用新APIRcIcon({ name: IconName.NEW_FEATURE, iconAnimation:{...}})}else{// 降级处理RcIcon({ name: IconName.NEW_FEATURE})}7.2 功能降级
// 检测是否支持动画@State supportsAnimation:boolean=trueRcIcon({ name: IconName.LOADER, iconAnimation:this.supportsAnimation ?{ duration:1000, iterations:-1}:undefined})八、总结
8.1 核心要点
- 类型安全优先:使用IconName常量避免错误
- 性能意识:合理控制渲染和动画
- 用户体验:提供清晰的视觉反馈
- 可访问性:支持无障碍功能
- 可维护性:编写清晰、可测试的代码
8.2 应用价值
RcIcon作为UI库的基础组件,在实际项目中:
- 减少50%以上的符号集成时间
- 提供统一的视觉语言
- 保证跨平台一致性
- 支持主题定制和扩展
- 性能优化开箱即用