054 — UI 组件复用策略:从 bill_card 到 asset_card 的业务组件封装
简介
在大型应用中,相同的 UI 模式往往出现在多个页面——账单列表页和账单详情页都需要展示账单卡片,资产总览页和资产编辑页都需要展示资产卡片。如果每个页面各自实现一套,不仅代码冗余,还会导致视觉和交互的不一致。MoneyTrack 将高频 UI 模式封装为独立的 HAR 组件包,如bill_card和asset_card,通过清晰的 props/events 接口定义实现复用。每个组件包还附带 README 使用文档和组件预览(preview)配置,让其他开发者可以快速上手。
核心知识点
1. 组件复用架构
组件复用架构的核心在于分层抽象,通过 mermaid 图可以清晰展示:
... ----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'subgraph', 'end', 'acc_title', 'acc_descr', 'acc_descr_multiline_value', 'AMP', 'COLON', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', 'direction_tb', 'direction_bt', 'direction_rl', 'direction_lr', 'direction_td', got 'LINK_ID'
组件层引用领域层的数据类型,消费层通过传入 @Prop 数据和控制 @Event 回调来使用组件。组件不关心数据的来源(网络/本地/状态管理),只负责渲染和交互反馈,实现了关注点分离。
2. props/events 接口定义
- @Prop:父组件传入的数据,支持单向数据流。
- @Link:双向绑定的数据传递。
- @Event(回调函数):子组件向父组件传递事件。
3. @Prop 多场景配置
一个成熟的组件需要覆盖多种使用场景——加载态、空态、错误态:
@Componentexportstruct BillInfoCard{// 数据属性@PropbillItem:BillModel|null;@Proploading:boolean=false;@Properror:string|null=null;// 展示配置@PropshowDate:boolean=true;@PropshowCategoryIcon:boolean=true;@Propcompact:boolean=false;// 事件回调onItemClick?:(id:string)=>void;onRetry?:()=>void;build(){Column(){if(this.loading){// 加载态:显示骨架屏LoadingSkeleton();}elseif(this.error){// 错误态:显示错误提示和重试按钮ErrorState({message:this.error,onRetry:this.onRetry});}elseif(!this.billItem){// 空态:显示占位提示EmptyState({message:'暂无账单数据'});}else{// 正常展示this.renderContent();}}}@BuilderrenderContent(){Row(){if(this.showCategoryIcon){CategoryIcon(this.billItem!.categoryId);}Column(){Text(this.billItem!.categoryName).fontSize(this.compact?14:16);if(this.showDate){Text(this.billItem!.date).fontSize(12);}}Text(this.billItem!.formatAmount()).fontSize(this.compact?16:20);}.onClick(()=>this.onItemClick?.(this.billItem!.id));}}这种设计让组件调用方可以灵活控制展示状态:列表页在加载数据时传入loading=true,请求失败时传入error信息,数据为空时 billItem 设为 null,组件自动渲染对应状态的 UI。
4. 组件预览(preview)配置
每个组件包在preview目录下配置预览页面,开发者可以在 IDE 中独立查看组件在不同状态下的表现:
// component_bill_card/preview/BillInfoCardPreview.ets@Entry@Componentstruct BillInfoCardPreview{build(){Scroll(){Column({space:16}){// 正常状态预览BillInfoCard({billItem:mockBillItem,showDate:true})// 加载态预览BillInfoCard({loading:true})// 空态预览BillInfoCard({billItem:null})// 异常态预览BillInfoCard({error:'网络异常,请重试',onRetry:()=>{/* 模拟重试 */}})}.padding(16)}}}5. 组件文档化
每个组件包附带 README 文档,说明组件的使用方式:
# @moneytrack/bill-card ## 安装 \`\`\` ohpm install @moneytrack/bill-card \`\`\` ## Props | 属性名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | billItem | BillModel | 否 | null | 账单数据,为null显示空态 | | loading | boolean | 否 | false | 是否显示加载态 | | error | string | 否 | null | 错误信息,不为null显示错误态 | | showDate | boolean | 否 | true | 是否显示日期 | | compact | boolean | 否 | false | 是否启用紧凑模式 | ## Events | 事件名 | 参数类型 | 说明 | |--------|----------|------| | onItemClick | (id: string) => void | 点击卡片时触发 | | onRetry | () => void | 错误态点击重试时触发 |6. 组件测试的基本思路
组件测试遵循"渲染测试 + 交互测试"两条主线:
- 渲染测试:传入不同的 @Prop 组合,验证组件正确渲染对应的 UI。例如传入
loading=true时验证骨架屏出现,传入空数组时验证空态占位图显示。 - 交互测试:模拟用户点击、滑动等操作,验证 @Event 回调是否正确触发。例如点击卡片验证
onItemClick是否被调用且参数正确。 - 截图对比:使用预览功能在不同设备尺寸下截图,确保组件布局在各分辨率下的表现一致。
项目代码案例
文件路径:component_bill_card/bill_card/src/main/ets/components/BillInfoCard.ets
完整代码见上文多场景配置示例。
文件路径:component_asset_card/src/main/ets/components/AssetCard.ets
@Componentexportstruct AssetCard{@Propasset:AssetModel;@Propcompact:boolean=false;build(){Column(){Text(this.asset.name).fontSize(this.compact?14:16);Text(this.asset.formatBalance()).fontSize(this.compact?16:20);}}}通过这套组件复用策略,相同样式的卡片在列表页和详情页保持一致,新增页面时只需引用对应组件并传入 props 即可。
推荐参考文档
- UI 组件封装指南
- @Prop / @Link 装饰器文档
- 组件预览配置指南