HarmonyOS7 自定义组件封装:从重复代码到优雅复用只需要3步 文章目录前言重复代码的痛3 步封装法第一步提取结构第二步定义接口第三步完善逻辑使用封装好的 Card 组件组件设计原则写在最后前言上个月接手一个项目打开代码一看——5 个页面每个页面都有一段几乎一样的卡片 UI 代码复制粘贴改改文字就完事。改需求的时候更惨5 个地方都得改一遍漏改一个就 bug。重复代码是技术债的起点而组件封装就是还债最快的方式。这篇文章分享我总结的 3 步封装法再通过一个完整的 Card 组件实战带你从重复代码走到优雅复用。重复代码的痛重复代码不只是看着不爽的问题它有三重罪改一处漏一处—— 5 个地方一样的代码改需求只改了 4 个第 5 个就是 bug代码膨胀—— 本来 200 行能搞定的事写了 800 行不敢重构—— 重复代码太多牵一发动全身谁都不敢动说白了不封装 每次改动都是定时炸弹。3 步封装法第一步提取结构把重复的 UI 结构抽出来做成独立的Component。封装前每个页面直接写卡片结构// 页面AColumn(){Image($r(app.media.pic1)).width(60).height(60).borderRadius(8)Text(标题A).fontSize(16).fontWeight(FontWeight.Bold)Text(描述A).fontSize(13).fontColor(#999)}.padding(16).backgroundColor(#fff).borderRadius(12)封装后抽成一个组件结构固定Componentstruct Card{build(){Column(){Image($r(app.media.pic1)).width(60).height(60).borderRadius(8)Text(标题A).fontSize(16).fontWeight(FontWeight.Bold)Text(描述A).fontSize(13).fontColor(#999)}.padding(16).backgroundColor(#fff).borderRadius(12)}}这一步只是搬了个家数据还是写死的。但已经有价值了——改结构只改一处。第二步定义接口把写死的数据改成参数让组件可配置。这就是组件的接口。改造后Componentstruct Card{Propicon:Resource$r(app.media.default)Proptitle:stringPropdesc:stringbuild(){Column(){Image(this.icon).width(60).height(60).borderRadius(8)Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)Text(this.desc).fontSize(13).fontColor(#999)}.padding(16).backgroundColor(#fff).borderRadius(12)}}关键代码讲解Prop icon: Resource—— 图片用Resource类型兼容$r()和网络图片Prop title: string —— 默认值设为空串避免不传参时显示 undefined用Prop而不是State因为数据来自父组件组件内部不需要拥有这一步之后组件就活了——传不同的参数展示不同的内容。第三步完善逻辑加入事件回调、条件渲染、样式定制等能力让组件真正通用。最终版本Componentstruct Card{Propicon:Resource$r(app.media.default)Proptitle:stringPropdesc:stringPropshowArrow:booleantrueonCardClick?:()voidbuild(){Row(){Image(this.icon).width(48).height(48).borderRadius(8).objectFit(ImageFit.Cover)Column(){Text(this.title).fontSize(16).fontWeight(FontWeight.Bold).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Text(this.desc).fontSize(13).fontColor(#999).margin({top:4}).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})}.layoutWeight(1).alignItems(HorizontalAlign.Start).margin({left:12})if(this.showArrow){Image($r(sys.media.ohos_ic_public_arrow_right)).width(16).height(16).fillColor(#ccc)}}.width(100%).padding(16).backgroundColor(#fff).borderRadius(12).onClick((){if(this.onCardClick){this.onCardClick()}})}}逐行讲解关键逻辑layoutWeight(1)—— 中间文字区域占满剩余空间图标和箭头固定大小maxLines(1)textOverflow—— 标题超长时显示省略号不会撑爆布局if (this.showArrow)—— 条件渲染不需要箭头的场景传showArrow: falseonCardClick?: () void—— 可选回调点击卡片时通知父组件onClick里的空值检查—— 回调是可选的不传就不触发避免运行时崩溃使用封装好的 Card 组件对比一下封装前 vs 封装后封装前页面里重复写// 页面A、页面B、页面C... 每个都写一坨Row(){Image(...).width(48).height(48)Column(){Text(...);Text(...)}Image(箭头).width(16)}.padding(16).borderRadius(12).onClick(...)封装后一行搞定Card({icon:$r(app.media.settings),title:设置,desc:应用设置与偏好,onCardClick:(){this.routerTo(Settings)}})代码量从 10 行变成 1 行改样式只改组件本身。这就是封装的价值。组件设计原则封装组件不是越多越好记住这几条两次重复就可以考虑封装—— 别等到第五次第二次就该动手接口要少而精—— 一个组件超过 8 个参数就该拆了提供合理默认值—— 让最常用的场景可以少传参回调用可选属性——onXxx?: () void不传不报错样式也能参数化—— 不只是数据圆角大小、内边距这些也可以暴露别过度封装—— 只出现一次的 UI 不用抽组件强行封装反而增加理解成本写在最后组件封装这件事说白了就是用一次的时间投入换以后每次的省心。3 步封装法——提取结构、定义接口、完善逻辑——听起来简单但很多人卡在第一步就不愿意动了。毕竟复制粘贴是最快的封装要动脑子。但相信我下次需求变更的时候你会感谢今天花 10 分钟封装的自己。