HarmonyOS开发实战:笔友-CommonComponents 组件库设计哲学——聚合与拆分的权衡
📅 2026/7/25 15:49:29
👁️ 阅读次数
📝 编程学习
前言
在 ArkUI 声明式开发范式中,组件复用是提升开发效率和 UI 一致性的关键手段。xiexin 将 5 个通用组件集中放在一个 146 行的CommonComponents.ets文件中,这种"单文件聚合"的设计在小型项目中极具性价比,但随着项目规模增长,需要向"多文件拆分"演进。
本文将以CommonComponents.ets为蓝本,详细剖析组件库设计中的"聚合 vs 拆分"选择、组件的导出/引用机制、@Component/@Prop/@BuilderParam在组件封装中的配合,以及组件库随着项目规模增长的演进路线。
一、CommonComponents 的整体设计
1.1 文件结构
xiexin 的CommonComponents.ets位于:
entry/src/main/ets/components/CommonComponents.ets整个文件 146 行,包含 5 个组件:
| 组件 | 行数 | 装饰器 | 用途 |
|---|---|---|---|
AvatarComponent | 34 | @Component@Prop | 头像显示,首字母回退 |
StatusBadge | 22 | @Component@Prop | 信件/笔友状态标签 |
CardContainer | 19 | @Component@BuilderParam | 通用卡片容器 |
EmptyState | 49 | @Component@Prop | 空状态占位 |
DividerLine | 10 | @Component@Prop | 分割线 |
1.2 所有组件代码
// entry/src/main/ets/components/CommonComponents.etsimport{AppColors}from'../common/Constants';// 通用头像组件@Componentexportstruct AvatarComponent{@Propname:string='';@PropavatarSize:number=48;@PropfontSize:number=18;privategetAvatarColor():string{constcolors:string[]=['#E8D5B7','#D4C4A8','#C9B896','#BFA98A','#D1C0A5','#C2B59B'];lethash:number=0;for(leti=0;i<this.name.length;i++){hash=this.name.charCodeAt(i)+((hash<<5)-hash);}returncolors[Math.abs(hash)%colors.length];}build(){Stack(){Circle().width(this.avatarSize).height(this.avatarSize).fill(this.getAvatarColor())Text(this.name.length>0?this.name.charAt(0):'?').fontSize(this.fontSize).fontColor(AppColors.TEXT_PRIMARY).fontWeight(FontWeight.Medium)}.width(this.avatarSize).height(this.avatarSize)}}// 状态标签组件@Componentexportstruct StatusBadge{@Proptext:string='';@Propcolor:string=AppColors.PRIMARY;@PropbgColor:string=AppColors.AMBER_LIGHT;build(){Text(this.text).fontSize(11).fontColor(this.color).backgroundColor(this.bgColor).borderRadius(10).padding({left:8,right:8,top:3,bottom:3}).fontWeight(FontWeight.Medium)}}// 通用卡片容器@Componentexportstruct CardContainer{@BuilderParamcontent:()=>void;@PropcardPadding:number=16;build(){Column(){this.content()}.width('100%').padding(this.cardPadding).backgroundColor(AppColors.CARD_BG).borderRadius(16).shadow({radius:8,color:'#0D000000',offsetX:0,offsetY:2})}}// 空状态组件@Componentexportstruct EmptyState{@Proptitle:string='暂无内容';@Propsubtitle:string='';@PropshowButton:boolean=false;@PropbuttonText:string='';onButtonClick?:()=>void;build(){Column({space:16}){Column(){Text('🏔').fontSize(64).opacity(0.3)}.margin({top:60})Text(this.title).fontSize(16).fontColor(AppColors.TEXT_SECONDARY)if(this.subtitle.length>0){Text(this.subtitle).fontSize(13).fontColor(AppColors.TEXT_SECONDARY).opacity(0.7)}if(this.showButton){Button(this.buttonText).fontSize(14).fontColor(AppColors.WHITE).backgroundColor(AppColors.PRIMARY).borderRadius(24).height(40).width(140).margin({top:16}).onClick(()=>{if(this.onButtonClick){this.onButtonClick();}})}}.width('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}}// 分割线组件@Componentexportstruct DividerLine{@PropmarginH:number=16;build(){Divider().strokeWidth(0.5).color(AppColors.DIVIDER).margin({left:this.marginH,right:this.marginH})}}二、单文件聚合 vs 多文件拆分
2.1 两种模式的对比
| 维度 | 单文件聚合(xiexin 当前) | 多文件拆分 |
|---|---|---|
| 文件数量 | 1 个 | N 个 |
| 代码定位 | 在同一文件中滚动 | 在目录中查找文件名 |
| 组件耦合 | 可见,便于发现耦合 | 隐藏,独立文件 |
| 导入语句 | 一次 import 全部 | 每个组件独立 import |
| 适合阶段 | 小型项目(< 10 组件) | 中大型项目(> 10 组件) |
2.2 导入方式的差异
// 单文件聚合:一次 import 全部import{AvatarComponent,StatusBadge,CardContainer,EmptyState,DividerLine}from'../components/CommonComponents';// 多文件拆分:每个组件独立 importimport{AvatarComponent}from'../components/AvatarComponent';import{StatusBadge}from'../components/StatusBadge';2.3 组件库目录结构演进
// 小型项目:单文件 components/CommonComponents.ets // 中型项目:按类别拆分 components/ ├── AvatarComponent.ets ├── StatusBadge.ets ├── CardContainer.ets ├── EmptyState.ets └── DividerLine.ets // 大型项目:按模块分组 components/ ├── avatars/ │ ├── AvatarComponent.ets │ └── GroupAvatar.ets ├── badges/ │ ├── StatusBadge.ets │ └── CountBadge.ets ├── cards/ │ ├── CardContainer.ets │ └── StatsCard.ets └── states/ └── EmptyState.ets三、@Component 的导出与引用
3.1 export struct 的语义
@Componentexportstruct AvatarComponent{@Propname:string='';@PropavatarSize:number=48;@PropfontSize:number=18;}3.2 在页面中引用
import{AvatarComponent,StatusBadge,EmptyState}from'../components/CommonComponents';@BuilderLetterCard(letter:Letter){Row(){AvatarComponent({name:letter.penPalName,avatarSize:40,fontSize:16})Column({space:6}){Text(letter.penPalName).fontSize(16)StatusBadge({text:this.getStatusText(letter),color:AppColors.PRIMARY})}}}四、@BuilderParam 插槽设计
4.1 CardContainer 的插槽
@Componentexportstruct CardContainer{@BuilderParamcontent:()=>void;@PropcardPadding:number=16;build(){Column(){this.content()}.width('100%').padding(this.cardPadding).backgroundColor(AppColors.CARD_BG).borderRadius(16).shadow({radius:8,color:'#0D000000',offsetX:0,offsetY:2})}}五、计算属性在组件中的使用
privategetAvatarColor():string{constcolors:string[]=['#E8D5B7','#D4C4A8','#C9B896','#BFA98A','#D1C0A5','#C2B59B'];lethash:number=0;for(leti=0;i<this.name.length;i++){hash=this.name.charCodeAt(i)+((hash<<5)-hash);}returncolors[Math.abs(hash)%colors.length];}六、条件渲染在组件中的应用
if(this.subtitle.length>0){Text(this.subtitle)}if(this.showButton){Button(this.buttonText)}七、组件参数设计原则
// 好的参数设计:明确的默认值@Propname:string='';@PropavatarSize:number=48;@PropfontSize:number=18;// 可选回调函数onButtonClick?:()=>void;八、组件库的测试策略
import{describe,it,expect}from'@ohos/hypium';describe('EmptyState',()=>{it('should display title when provided',()=>{constcomponent=newEmptyState();component.title='测试标题';expect(component.title).toBe('测试标题');});});九、组件的扩展建议
@Componentexportstruct AvatarComponent{@Propname:string='';@PropavatarUrl:string='';@PropavatarSize:number=48;@PropfontSize:number=18;build(){Stack(){if(this.avatarUrl.length>0){Image(this.avatarUrl).width(this.avatarSize).height(this.avatarSize).borderRadius(this.avatarSize/2)}else{Circle().width(this.avatarSize).height(this.avatarSize).fill(this.getAvatarColor())Text(this.name.charAt(0)).fontSize(this.fontSize).fontColor(AppColors.TEXT_PRIMARY)}}.width(this.avatarSize).height(this.avatarSize)}}九、组件库的版本管理策略
当组件库需要版本迭代时,推荐以下策略:
- 新增组件:在 CommonComponents 文件中新增组件,不影响现有组件
- 修改组件:修改
@Prop参数时,考虑向后兼容性 - 废弃组件:保留旧接口,添加
@deprecated注释
/** * @deprecated 请使用 NewAvatarComponent 替代 */@Componentexportstruct AvatarComponent{// 旧接口}十、组件库的文档化
组件库的文档化是团队协作的关键:
- 每个组件需要标注
@Prop参数说明 - 提供使用示例代码
- 标注不兼容的变更
/** * 头像组件 * @param name 用户名,用于首字母和颜色计算 * @param avatarSize 头像尺寸(默认 48) * @param fontSize 首字母字号(默认 18) */@Componentexportstruct AvatarComponent{@Propname:string='';@PropavatarSize:number=48;@PropfontSize:number=18;}十一、从 xiexin 看组件库设计
xiexin 的组件库设计体现了"够用就好"的原则:
- 单文件聚合:5 个组件,146 行,适合当前阶段
- @Prop 参数化:所有组件可通过参数定制
- @BuilderParam 插槽:CardContainer 支持内容注入
- 可选回调:EmptyState 的 onButtonClick 可选
提示:当组件数量超过 10 个时,建议按"组件类型"拆分到独立文件,避免单文件膨胀。
十二、组件库的 CI/CD 集成
在团队协作中,组件库的自动化测试和发布是保证质量的关键:
- 单元测试:每个组件需要有对应的
@ohos/hypium测试用例 - 视觉回归测试:使用截图对比工具检测 UI 变化
- 自动发布:组件库可以发布为 HAR 包,供其他模块使用
// oh-package.json5 { "dependencies": { "@xiexin/common-components": "1.0.0" } }总结
本文详细剖析了 xiexin 的 CommonComponents 组件库设计哲学,重点讲解了单文件聚合 vs 多文件拆分的权衡、@Prop 参数化设计、@BuilderParam 插槽模式,以及组件库随着项目规模增长的演进路线。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 应用开发指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-dev-guide
- HarmonyOS 状态管理概述:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview
- HarmonyOS 高性能编程实践:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-high-performance-programming
- HarmonyOS 组件复用:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-component-reusable
- HarmonyOS 自定义组件:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-custom-components
- HarmonyOS 组件封装:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-component-encapsulation
- HarmonyOS @Builder 装饰器:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder
- HarmonyOS @BuilderParam 装饰器:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builderparam
- HarmonyOS 自定义组件:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-custom-components
- HarmonyOS 组件封装:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-component-encapsulation
- HarmonyOS @Builder 装饰器:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder
- HarmonyOS @BuilderParam 装饰器:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builderparam
- HarmonyOS @Prop 装饰器:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-prop
- HarmonyOS 组件复用开发实践:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-component_reuse
- HarmonyOS 状态管理概述:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview
- HarmonyOS 自定义组件生命周期:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-custom-components-lifecycle
编程学习
技术分享
实战经验