HarmonyOS 6.0 样式复用与@Styles/@Builder
写ArkUI代码最烦的就是重复——卡片样式、按钮样式、列表项模板,复制粘贴一多维护就崩。HarmonyOS NEXT给了三种复用机制:@Styles管属性、@Builder管组件、@Extend管扩展,各有各的适用场景和限制。
@Styles — 属性级复用
@Styles把一组通用属性抽取出来,当成一个属性方法调用:
@Styles
fancyCard(): 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只能在该组件使用;全局定义的可以被所有组件共享。
-
支持无参和带参数 — 带参数版本可以实现动态样式:
@Styles
cardWithRadius(radius: number): void {
.borderRadius(radius)
.padding(16)
.backgroundColor('#ffffff')
}
// 使用
Column() { ... }.cardWithRadius(8)
Column() { ... }.cardWithRadius(16)
常见错误
// 错误1: 在@Styles里用组件特有属性
@Styles
titleText(): void {
.fontSize(16) // Text特有属性,不属于CommonAttribute
.fontWeight(FontWeight.Bold)
}
// 错误2: 在@Styles后链式调用
Column() { ... }
.fancyCard()
.width('100%') // 编译报错:Property 'width' does not exist on type 'void'
// 正确: width写在@Styles内部
@Styles
fancyCard(): void {
.width('100%')
.padding(16)
.borderRadius(12)
.backgroundColor('#ffffff')
}
@Builder — 组件级复用
@Builder能封装完整的UI片段,包含子组件和布局,这是@Styles做不到的:
@Builder
InfoRow(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的参数传递有两种方式,行为不同:
按值传递(基本类型参数):值在调用时拷贝,后续修改不影响已渲染的UI:
@Builder
PresetButton(preset: StylePreset, index: number): void {
Button(preset.name)
.backgroundColor(this.currentPreset === index ? '#1a73e8' : '#cccccc')
}
this.PresetButton(this.presets[0], 0)
按引用传递(对象参数加:前缀):传递引用,UI能响应对象变化:
@Builder
ItemBuilder(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访问状态,这是最大的优势:
@Component
struct MyPage {
@State isActive: boolean = false;
@Builder
StatusBadge(): void {
Row() {
Text(this.isActive ? '激活' : '未激活') // 直接用this
.fontColor(this.isActive ? '#43a047' : '#e53935')
}
}
}
@Extend — 扩展原生组件
@Extend给原生组件添加自定义属性方法:
@Extend(Text)
function titleText(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)
function primaryButton(): void {
.backgroundColor('#1a73e8')
.fontColor('#ffffff')
.borderRadius(8)
.fontSize(14)
}
@Extend(Button)
function dangerButton(): void {
.backgroundColor('#e53935')
.fontColor('#ffffff')
.borderRadius(8)
.fontSize(14)
}
Button('确认').primaryButton()
Button('删除').dangerButton()

三种机制对比
| 特性 | @Styles | @Builder | @Extend |
|---|---|---|---|
| 定义位置 | struct内或全局 | struct内或全局 | 仅全局 |
| 包含子组件 | 不可以 | 可以 | 不可以 |
| 支持参数 | 支持 | 支持 | 支持 |
| 适用范围 | 所有组件 | 所有组件 | 指定组件 |
| 返回值 | void | void | void |
| 调用方式 | .styleName() | this.builderName() | .extendName() |
| 属性链式 | 定义内部链式 | 自由 | 定义内部链式 |
实际选型指南
卡片样式统一 → @Styles
@Styles
cardStyle(): void {
.width('100%')
.padding(16)
.borderRadius(12)
.backgroundColor('#ffffff')
.shadow({ radius: 4, color: '#1a000000', offsetY: 2 })
}
列表项模板 → @Builder
@Builder
ListItemTemplate(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')
}
按钮风格变体 → @Extend
@Extend(Button)
function btnPrimary(): void {
.backgroundColor('#1a73e8').fontColor('#ffffff').borderRadius(6)
}
组合使用
实际项目中三种机制经常组合使用:
@Styles
cardBase(): void {
.width('100%')
.padding(16)
.borderRadius(12)
.backgroundColor('#ffffff')
}
@Builder
InfoCard(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 | 改用@Builder |
| @Styles后链式.width报错 | @Styles返回void | 把属性移入@Styles定义 |
| @Extend写在struct内报错 | @Extend只能全局定义 | 移到struct外面 |
| @Builder里ForEach不刷新 | key函数返回值重复 | 确保key唯一 |
| 全局@Builder访问不到this | 全局没有组件上下文 | 改用成员@Builder |
| @Styles参数不生效 | 参数类型不支持 | 检查参数类型 |
样式复用的核心原则:能用@Styles解决的不用@Builder,能用@Builder解决的不用@Extend。@Styles最轻量,@Builder最灵活,@Extend最精确。三者各有领地,别跨着用——用@Builder做属性复用是大材小用,用@Styles做组件模板是做不到。
更多推荐


所有评论(0)