写ArkUI代码最烦的就是重复——卡片样式、按钮样式、列表项模板复制粘贴一多维护就崩。HarmonyOS NEXT给了三种复用机制Styles管属性、Builder管组件、Extend管扩展各有各的适用场景和限制。Styles — 属性级复用Styles把一组通用属性抽取出来当成一个属性方法调用StylesfancyCard():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff).shadow({radius:4,color:#1a000000,offsetY:2}).margin({bottom:12})}// 使用Column(){Text(内容)}.fancyCard()核心限制只能用通用属性— .padding、.borderRadius、.backgroundColor、.width、.margin、.shadow这些属于CommonAttribute的属性可以用。组件特有属性如Text的.fontSize、.fontColor不能出现在Styles里。返回void不能链式调用— .fancyCard()返回void后面不能再接.width()或.margin()。所有需要的属性都要写在Styles定义内部。可以定义在struct内或全局— struct内定义的Styles只能在该组件使用全局定义的可以被所有组件共享。支持无参和带参数— 带参数版本可以实现动态样式StylescardWithRadius(radius:number):void{.borderRadius(radius).padding(16).backgroundColor(#ffffff)}// 使用Column(){...}.cardWithRadius(8)Column(){...}.cardWithRadius(16)常见错误// 错误1: 在Styles里用组件特有属性StylestitleText():void{.fontSize(16)// Text特有属性不属于CommonAttribute.fontWeight(FontWeight.Bold)}// 错误2: 在Styles后链式调用Column(){...}.fancyCard().width(100%)// 编译报错Property width does not exist on type void// 正确: width写在Styles内部StylesfancyCard():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff)}Builder — 组件级复用Builder能封装完整的UI片段包含子组件和布局这是Styles做不到的BuilderInfoRow(label:string,value:string):void{Row(){Text(label).fontSize(13).fontColor(#888888).width(80)Text(value).fontSize(13).fontColor(#333333).layoutWeight(1)}.width(100%).padding({top:6,bottom:6})}// 使用Column(){this.InfoRow(名称,HarmonyOS)this.InfoRow(版本,API 12)this.InfoRow(语言,ArkTS)}按值传递 vs 按引用传递Builder的参数传递有两种方式行为不同按值传递基本类型参数值在调用时拷贝后续修改不影响已渲染的UIBuilderPresetButton(preset:StylePreset,index:number):void{Button(preset.name).backgroundColor(this.currentPresetindex?#1a73e8:#cccccc)}this.PresetButton(this.presets[0],0)按引用传递对象参数加:前缀传递引用UI能响应对象变化BuilderItemBuilder(item:Item):void{// item是引用变化时会更新Text(item.name)}this.ItemBuilder({name:test})// 传对象字面量注意按引用传递的对象必须是class/interface类型不能用基本类型。全局Builder vs 成员Builder成员Builder— 定义在struct内用this.builderName()调用可以访问组件的State变量全局Builder— 定义在struct外用builderName()调用不能访问组件状态成员Builder能直接用this访问状态这是最大的优势Componentstruct MyPage{StateisActive:booleanfalse;BuilderStatusBadge():void{Row(){Text(this.isActive?激活:未激活)// 直接用this.fontColor(this.isActive?#43a047:#e53935)}}}Extend — 扩展原生组件Extend给原生组件添加自定义属性方法Extend(Text)functiontitleText(size:number):void{.fontSize(size).fontWeight(FontWeight.Bold).fontColor(#333333)}// 使用Text(标题).titleText(20)Text(副标题).titleText(16)核心限制只能在全局作用域定义— 不能写在struct内部这是跟Styles/Builder最大的区别只能扩展指定组件—Extend(Text)只能给Text组件添加方法不能给Column用只支持属性方法— 跟Styles类似只能加属性链不能加子组件支持参数— 可以传参实现动态样式// 按钮变体Extend(Button)functionprimaryButton():void{.backgroundColor(#1a73e8).fontColor(#ffffff).borderRadius(8).fontSize(14)}Extend(Button)functiondangerButton():void{.backgroundColor(#e53935).fontColor(#ffffff).borderRadius(8).fontSize(14)}Button(确认).primaryButton()Button(删除).dangerButton()三种机制对比特性StylesBuilderExtend定义位置struct内或全局struct内或全局仅全局包含子组件不可以可以不可以支持参数支持支持支持适用范围所有组件所有组件指定组件返回值voidvoidvoid调用方式.styleName()this.builderName().extendName()属性链式定义内部链式自由定义内部链式实际选型指南卡片样式统一→ StylesStylescardStyle():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff).shadow({radius:4,color:#1a000000,offsetY:2})}列表项模板→ BuilderBuilderListItemTemplate(title:string,desc:string,tag:string):void{Row(){Text(title).fontSize(14).layoutWeight(1)Text(tag).fontSize(10).fontColor(#1a73e8)}.width(100%).padding(12).borderRadius(8).backgroundColor(#ffffff)}按钮风格变体→ ExtendExtend(Button)functionbtnPrimary():void{.backgroundColor(#1a73e8).fontColor(#ffffff).borderRadius(6)}组合使用实际项目中三种机制经常组合使用StylescardBase():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff)}BuilderInfoCard(title:string,items:string[]):void{Column(){Text(title).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333333)ForEach(items,(item:string){this.InfoRow(item.substring(0,item.indexOf(:)),item.substring(item.indexOf(:)1))},(item:string,index:number)index.toString())}.cardBase()// Styles Builder组合}Builder里调用Styles完全没问题——Styles是属性方法Builder是组件方法它们在不同层级工作。踩坑清单问题原因解决Styles里用.fontSize报错fontSize不是CommonAttribute改用BuilderStyles后链式.width报错Styles返回void把属性移入Styles定义Extend写在struct内报错Extend只能全局定义移到struct外面Builder里ForEach不刷新key函数返回值重复确保key唯一全局Builder访问不到this全局没有组件上下文改用成员BuilderStyles参数不生效参数类型不支持检查参数类型样式复用的核心原则能用Styles解决的不用Builder能用Builder解决的不用Extend。Styles最轻量Builder最灵活Extend最精确。三者各有领地别跨着用——用Builder做属性复用是大材小用用Styles做组件模板是做不到。
HarmonyOS 6.0 样式复用与@Styles/@Builder
写ArkUI代码最烦的就是重复——卡片样式、按钮样式、列表项模板复制粘贴一多维护就崩。HarmonyOS NEXT给了三种复用机制Styles管属性、Builder管组件、Extend管扩展各有各的适用场景和限制。Styles — 属性级复用Styles把一组通用属性抽取出来当成一个属性方法调用StylesfancyCard():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff).shadow({radius:4,color:#1a000000,offsetY:2}).margin({bottom:12})}// 使用Column(){Text(内容)}.fancyCard()核心限制只能用通用属性— .padding、.borderRadius、.backgroundColor、.width、.margin、.shadow这些属于CommonAttribute的属性可以用。组件特有属性如Text的.fontSize、.fontColor不能出现在Styles里。返回void不能链式调用— .fancyCard()返回void后面不能再接.width()或.margin()。所有需要的属性都要写在Styles定义内部。可以定义在struct内或全局— struct内定义的Styles只能在该组件使用全局定义的可以被所有组件共享。支持无参和带参数— 带参数版本可以实现动态样式StylescardWithRadius(radius:number):void{.borderRadius(radius).padding(16).backgroundColor(#ffffff)}// 使用Column(){...}.cardWithRadius(8)Column(){...}.cardWithRadius(16)常见错误// 错误1: 在Styles里用组件特有属性StylestitleText():void{.fontSize(16)// Text特有属性不属于CommonAttribute.fontWeight(FontWeight.Bold)}// 错误2: 在Styles后链式调用Column(){...}.fancyCard().width(100%)// 编译报错Property width does not exist on type void// 正确: width写在Styles内部StylesfancyCard():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff)}Builder — 组件级复用Builder能封装完整的UI片段包含子组件和布局这是Styles做不到的BuilderInfoRow(label:string,value:string):void{Row(){Text(label).fontSize(13).fontColor(#888888).width(80)Text(value).fontSize(13).fontColor(#333333).layoutWeight(1)}.width(100%).padding({top:6,bottom:6})}// 使用Column(){this.InfoRow(名称,HarmonyOS)this.InfoRow(版本,API 12)this.InfoRow(语言,ArkTS)}按值传递 vs 按引用传递Builder的参数传递有两种方式行为不同按值传递基本类型参数值在调用时拷贝后续修改不影响已渲染的UIBuilderPresetButton(preset:StylePreset,index:number):void{Button(preset.name).backgroundColor(this.currentPresetindex?#1a73e8:#cccccc)}this.PresetButton(this.presets[0],0)按引用传递对象参数加:前缀传递引用UI能响应对象变化BuilderItemBuilder(item:Item):void{// item是引用变化时会更新Text(item.name)}this.ItemBuilder({name:test})// 传对象字面量注意按引用传递的对象必须是class/interface类型不能用基本类型。全局Builder vs 成员Builder成员Builder— 定义在struct内用this.builderName()调用可以访问组件的State变量全局Builder— 定义在struct外用builderName()调用不能访问组件状态成员Builder能直接用this访问状态这是最大的优势Componentstruct MyPage{StateisActive:booleanfalse;BuilderStatusBadge():void{Row(){Text(this.isActive?激活:未激活)// 直接用this.fontColor(this.isActive?#43a047:#e53935)}}}Extend — 扩展原生组件Extend给原生组件添加自定义属性方法Extend(Text)functiontitleText(size:number):void{.fontSize(size).fontWeight(FontWeight.Bold).fontColor(#333333)}// 使用Text(标题).titleText(20)Text(副标题).titleText(16)核心限制只能在全局作用域定义— 不能写在struct内部这是跟Styles/Builder最大的区别只能扩展指定组件—Extend(Text)只能给Text组件添加方法不能给Column用只支持属性方法— 跟Styles类似只能加属性链不能加子组件支持参数— 可以传参实现动态样式// 按钮变体Extend(Button)functionprimaryButton():void{.backgroundColor(#1a73e8).fontColor(#ffffff).borderRadius(8).fontSize(14)}Extend(Button)functiondangerButton():void{.backgroundColor(#e53935).fontColor(#ffffff).borderRadius(8).fontSize(14)}Button(确认).primaryButton()Button(删除).dangerButton()三种机制对比特性StylesBuilderExtend定义位置struct内或全局struct内或全局仅全局包含子组件不可以可以不可以支持参数支持支持支持适用范围所有组件所有组件指定组件返回值voidvoidvoid调用方式.styleName()this.builderName().extendName()属性链式定义内部链式自由定义内部链式实际选型指南卡片样式统一→ StylesStylescardStyle():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff).shadow({radius:4,color:#1a000000,offsetY:2})}列表项模板→ BuilderBuilderListItemTemplate(title:string,desc:string,tag:string):void{Row(){Text(title).fontSize(14).layoutWeight(1)Text(tag).fontSize(10).fontColor(#1a73e8)}.width(100%).padding(12).borderRadius(8).backgroundColor(#ffffff)}按钮风格变体→ ExtendExtend(Button)functionbtnPrimary():void{.backgroundColor(#1a73e8).fontColor(#ffffff).borderRadius(6)}组合使用实际项目中三种机制经常组合使用StylescardBase():void{.width(100%).padding(16).borderRadius(12).backgroundColor(#ffffff)}BuilderInfoCard(title:string,items:string[]):void{Column(){Text(title).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333333)ForEach(items,(item:string){this.InfoRow(item.substring(0,item.indexOf(:)),item.substring(item.indexOf(:)1))},(item:string,index:number)index.toString())}.cardBase()// Styles Builder组合}Builder里调用Styles完全没问题——Styles是属性方法Builder是组件方法它们在不同层级工作。踩坑清单问题原因解决Styles里用.fontSize报错fontSize不是CommonAttribute改用BuilderStyles后链式.width报错Styles返回void把属性移入Styles定义Extend写在struct内报错Extend只能全局定义移到struct外面Builder里ForEach不刷新key函数返回值重复确保key唯一全局Builder访问不到this全局没有组件上下文改用成员BuilderStyles参数不生效参数类型不支持检查参数类型样式复用的核心原则能用Styles解决的不用Builder能用Builder解决的不用Extend。Styles最轻量Builder最灵活Extend最精确。三者各有领地别跨着用——用Builder做属性复用是大材小用用Styles做组件模板是做不到。