ArkTS 样式结构重用三大装饰器详解
ArkTS样式复用三大装饰器对比摘要 ArkTS提供@Styles、@Extend和@Builder三大装饰器实现代码复用: @Styles - 仅封装通用样式属性,不支持参数和组件特有属性,可全局/局部定义 @Extend - 扩展特定组件样式,支持参数传递(核心优势),只能全局定义 @Builder - 复用完整UI结构,支持参数传递和逻辑控制,灵活性最高 三者在参数支持、作用范围和应用场景上
·
ArkTS 样式结构重用三大装饰器详解
概述
在 ArkTS 开发中,有三个核心装饰器用于提高代码复用性:
- @Styles - 样式复用(仅属性)
- @Extend - 组件扩展(仅属性)
- @Builder - 结构复用(UI 结构)
这三个装饰器各有特点,合理使用可以大幅提升开发效率和代码可维护性。
一、@Styles 装饰器
1.1 基本概念
@Styles 用于提取公共属性样式,将多个通用样式属性封装成一个方法,实现样式的复用。
1.2 核心特点
- ✅ 提取公共样式属性
- ✅ 可全局定义或组件内定义
- ✅ 支持状态变量
- ❌ 不支持参数传递
- ❌ 只能设置通用属性(所有组件共有的属性)
- ❌ 不能包含 UI 结构
1.3 语法格式
// 全局定义(使用 function 关键字)
@Styles function globalStyleName() {
.width(200)
.height(100)
.backgroundColor(Color.Red)
}
// 组件内定义(不使用 function 关键字)
@Component
struct MyComponent {
@Styles localStyleName() {
.width(200)
.height(100)
.backgroundColor(Color.Blue)
}
}
1.4 使用示例
// 全局样式定义
@Styles function commonCardStyle() {
.width('90%')
.height(120)
.backgroundColor(Color.White)
.borderRadius(12)
.padding(16)
.shadow({ radius: 8, color: '#00000020' })
}
@Entry
@Component
struct StylesDemo {
// 组件内样式定义
@Styles primaryTextStyle() {
.fontSize(16)
.fontColor('#333333')
.fontWeight(FontWeight.Medium)
}
build() {
Column({ space: 20 }) {
// 使用全局样式
Column() {
Text('卡片内容')
}
.commonCardStyle()
// 使用组件内样式
Text('标题文字')
.primaryTextStyle()
// 样式可以叠加和覆盖
Text('特殊文字')
.primaryTextStyle()
.fontColor(Color.Red) // 覆盖颜色
}
.width('100%')
.padding(20)
}
}
1.5 使用限制
// ❌ 错误:不能传递参数
@Styles function wrongStyle(color: Color) { // 编译错误
.backgroundColor(color)
}
// ❌ 错误:不能设置组件特有属性
@Styles function wrongTextStyle() {
.text('Hello') // 编译错误,text 不是通用属性
}
// ✅ 正确:只使用通用属性
@Styles function correctStyle() {
.width(100)
.height(100)
.backgroundColor(Color.Blue)
}
二、@Extend 装饰器
2.1 基本概念
@Extend 用于扩展特定组件的样式,可以为指定组件类型添加自定义样式方法,并且支持参数传递。
2.2 核心特点
- ✅ 支持参数传递(核心优势)
- ✅ 参数可以是状态变量
- ✅ 可设置组件特有属性
- ✅ 针对特定组件类型
- ❌ 只能全局定义
- ❌ 不能包含 UI 结构
- ❌ 只能扩展一个组件类型
2.3 语法格式
// 必须在组件外部定义
@Extend(ComponentType) function extendMethodName(param1: type1, param2: type2) {
.attribute1(param1)
.attribute2(param2)
}
2.4 使用示例
基础参数化样式
// 扩展 Text 组件
@Extend(Text) function fancyText(size: number, color: ResourceColor, weight: FontWeight) {
.fontSize(size)
.fontColor(color)
.fontWeight(weight)
.textAlign(TextAlign.Center)
.lineHeight(size * 1.5)
}
// 扩展 Button 组件
@Extend(Button) function themeButton(bgColor: ResourceColor, size: 'small' | 'medium' | 'large') {
.backgroundColor(bgColor)
.borderRadius(8)
.width(size === 'small' ? 80 : size === 'medium' ? 120 : 160)
.height(size === 'small' ? 32 : size === 'medium' ? 40 : 48)
}
@Entry
@Component
struct ExtendDemo {
build() {
Column({ space: 20 }) {
// 使用扩展方法
Text('大标题')
.fancyText(24, '#333333', FontWeight.Bold)
Text('副标题')
.fancyText(18, '#666666', FontWeight.Medium)
Button('小按钮')
.themeButton('#007DFF', 'small')
Button('大按钮')
.themeButton('#FF6B6B', 'large')
}
.width('100%')
.padding(20)
}
}
支持状态变量
@Extend(Column) function cardContainer(
bgColor: ResourceColor,
isSelected: boolean,
elevation: number
) {
.backgroundColor(bgColor)
.borderRadius(12)
.padding(16)
.border({
width: isSelected ? 2 : 0,
color: '#007DFF'
})
.shadow({
radius: elevation,
color: isSelected ? '#007DFF40' : '#00000020'
})
}
@Entry
@Component
struct ExtendStateDemo {
@State selectedIndex: number = 0
@State items: string[] = ['选项1', '选项2', '选项3']
build() {
Column({ space: 16 }) {
ForEach(this.items, (item: string, index: number) => {
Column() {
Text(item)
.fontSize(16)
}
.width('100%')
.height(80)
.cardContainer(
Color.White,
this.selectedIndex === index, // 状态变量
this.selectedIndex === index ? 16 : 8 // 根据状态动态调整
)
.onClick(() => {
this.selectedIndex = index
})
})
}
.width('100%')
.padding(20)
}
}
扩展事件和手势
@Extend(Text) function clickableText(
normalColor: ResourceColor,
pressedColor: ResourceColor,
onClick: () => void
) {
.fontColor(normalColor)
.onClick(onClick)
.stateStyles({
normal: {
.fontColor(normalColor)
},
pressed: {
.fontColor(pressedColor)
}
})
}
@Entry
@Component
struct ExtendEventDemo {
@State message: string = ''
build() {
Column({ space: 20 }) {
Text('点击我')
.fontSize(18)
.clickableText(
'#007DFF',
'#0056B3',
() => {
this.message = '文字被点击了'
}
)
Text(this.message)
.fontSize(14)
.fontColor('#999')
}
.width('100%')
.padding(20)
}
}
三、@Builder 装饰器
3.1 基本概念
@Builder 用于UI 结构的复用,可以将一段 UI 结构封装成方法,实现复杂视图的重复使用。
3.2 核心特点
- ✅ 复用 UI 结构(核心优势)
- ✅ 支持参数传递
- ✅ 可全局定义或组件内定义
- ✅ 支持状态变量
- ✅ 可以包含逻辑判断
- ✅ 可以嵌套其他 @Builder
- ✅ 支持尾随闭包语法
3.3 语法格式
// 全局定义
@Builder function globalBuilderName(params) {
// UI 结构
}
// 组件内定义
@Component
struct MyComponent {
@Builder localBuilderName(params) {
// UI 结构
}
}
3.4 使用示例
基础 UI 结构复用
// 全局 Builder
@Builder function IconTextItem(icon: Resource, text: string, color: ResourceColor) {
Row({ space: 8 }) {
Image(icon)
.width(24)
.height(24)
Text(text)
.fontSize(16)
.fontColor(color)
}
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
@Entry
@Component
struct BuilderDemo {
build() {
Column({ space: 12 }) {
IconTextItem($r('app.media.home'), '首页', '#333333')
IconTextItem($r('app.media.message'), '消息', '#666666')
IconTextItem($r('app.media.profile'), '我的', '#999999')
}
.width('100%')
.padding(20)
}
}
组件内 Builder(访问组件状态)
@Entry
@Component
struct BuilderStateDemo {
@State count: number = 0
@State isExpanded: boolean = false
// 组件内 Builder 可以访问组件的状态变量
@Builder CounterCard() {
Column({ space: 16 }) {
Text(`当前计数: ${this.count}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Row({ space: 12 }) {
Button('-')
.width(60)
.onClick(() => this.count--)
Button('+')
.width(60)
.onClick(() => this.count++)
}
if (this.isExpanded) {
Text('详细信息区域')
.fontSize(14)
.fontColor('#666')
.padding(12)
.backgroundColor('#F0F0F0')
.borderRadius(4)
}
Button(this.isExpanded ? '收起' : '展开')
.onClick(() => {
this.isExpanded = !this.isExpanded
})
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 8, color: '#00000020' })
}
build() {
Column() {
this.CounterCard()
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
复杂列表项 Builder
interface ListItemData {
title: string
subtitle: string
icon: Resource
badge?: number
isNew?: boolean
}
@Builder function ComplexListItem(data: ListItemData, onClick: () => void) {
Row({ space: 12 }) {
// 图标
Image(data.icon)
.width(48)
.height(48)
.borderRadius(24)
// 文字信息
Column({ space: 4 }) {
Row({ space: 8 }) {
Text(data.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#333333')
if (data.isNew) {
Text('NEW')
.fontSize(10)
.fontColor(Color.White)
.backgroundColor('#FF6B6B')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
}
}
Text(data.subtitle)
.fontSize(14)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 徽章
if (data.badge && data.badge > 0) {
Text(data.badge > 99 ? '99+' : data.badge.toString())
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#FF3B30')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(10)
.constraintSize({ minWidth: 20 })
.textAlign(TextAlign.Center)
}
Image($r('app.media.arrow_right'))
.width(20)
.height(20)
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.onClick(onClick)
}
@Entry
@Component
struct ComplexBuilderDemo {
@State items: ListItemData[] = [
{ title: '消息中心', subtitle: '3条新消息', icon: $r('app.media.message'), badge: 3, isNew: false },
{ title: '系统通知', subtitle: '重要更新', icon: $r('app.media.notification'), badge: 1, isNew: true },
{ title: '个人设置', subtitle: '账号与安全', icon: $r('app.media.settings'), badge: 0 }
]
build() {
Column({ space: 12 }) {
ForEach(this.items, (item: ListItemData, index: number) => {
ComplexListItem(item, () => {
console.log(`点击了: ${item.title}`)
})
})
}
.width('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
尾随闭包语法(容器组件)
@Builder function CustomCard(title: string, @BuilderParam content: () => void) {
Column({ space: 12 }) {
// 卡片标题
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.width('100%')
Divider()
// 动态内容区域
content()
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 8, color: '#00000020' })
}
@Entry
@Component
struct BuilderParamDemo {
@State username: string = '张三'
@State score: number = 98
build() {
Column({ space: 20 }) {
// 使用尾随闭包传入自定义内容
CustomCard('用户信息') {
Column({ space: 8 }) {
Text(`姓名: ${this.username}`)
.fontSize(16)
Text(`分数: ${this.score}`)
.fontSize(16)
.fontColor('#007DFF')
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
CustomCard('操作按钮') {
Row({ space: 12 }) {
Button('编辑')
.width(100)
Button('删除')
.width(100)
.backgroundColor('#FF6B6B')
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
条件渲染 Builder
@Entry
@Component
struct ConditionalBuilderDemo {
@State isLoading: boolean = true
@State hasError: boolean = false
@State data: string[] = []
@Builder LoadingView() {
Column() {
LoadingProgress()
.width(50)
.height(50)
Text('加载中...')
.fontSize(14)
.fontColor('#999')
.margin({ top: 12 })
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
}
@Builder ErrorView() {
Column() {
Image($r('app.media.error'))
.width(80)
.height(80)
Text('加载失败')
.fontSize(16)
.fontColor('#FF6B6B')
.margin({ top: 12 })
Button('重试')
.margin({ top: 20 })
.onClick(() => {
this.loadData()
})
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
}
@Builder EmptyView() {
Column() {
Image($r('app.media.empty'))
.width(100)
.height(100)
Text('暂无数据')
.fontSize(14)
.fontColor('#999')
.margin({ top: 12 })
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.Center)
}
@Builder ContentView() {
Column({ space: 12 }) {
ForEach(this.data, (item: string) => {
Text(item)
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
})
}
.width('100%')
}
@Builder ContentContainer() {
if (this.isLoading) {
this.LoadingView()
} else if (this.hasError) {
this.ErrorView()
} else if (this.data.length === 0) {
this.EmptyView()
} else {
this.ContentView()
}
}
loadData() {
this.isLoading = true
this.hasError = false
// 模拟网络请求
setTimeout(() => {
this.isLoading = false
this.data = ['数据1', '数据2', '数据3']
}, 2000)
}
aboutToAppear() {
this.loadData()
}
build() {
Column() {
this.ContentContainer()
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
四、三者对比总结
4.1 核心区别表
| 对比维度 | @Styles | @Extend | @Builder |
|---|---|---|---|
| 主要用途 | 样式属性复用 | 组件样式扩展 | UI 结构复用 |
| 定义位置 | 全局或组件内 | 仅全局 | 全局或组件内 |
| 参数支持 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 状态变量 | ✅ 支持内部使用 | ✅ 支持作为参数 | ✅ 支持 |
| 组件限定 | ❌ 通用属性 | ✅ 指定组件类型 | ❌ 不限定 |
| UI 结构 | ❌ 不能包含 | ❌ 不能包含 | ✅ 可以包含 |
| 特有属性 | ❌ 不能设置 | ✅ 可以设置 | ✅ 可以设置 |
| 尾随闭包 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
| 适用场景 | 固定的公共样式 | 参数化的组件样式 | 复杂 UI 结构复用 |
| 灵活性 | 低 | 中 | 高 |
| 复杂度 | 低 | 中 | 高 |
4.2 使用场景对比
// 场景1: 固定的样式组合 → 使用 @Styles
@Styles function cardShadow() {
.shadow({ radius: 8, color: '#00000020', offsetY: 2 })
.backgroundColor(Color.White)
.borderRadius(12)
}
// 场景2: 需要参数的样式 → 使用 @Extend
@Extend(Text) function themeText(level: 1 | 2 | 3) {
.fontSize(level === 1 ? 24 : level === 2 ? 18 : 14)
.fontWeight(level === 1 ? FontWeight.Bold : FontWeight.Medium)
.fontColor('#333333')
}
// 场景3: 复杂的UI结构 → 使用 @Builder
@Builder function UserCard(name: string, avatar: Resource, status: 'online' | 'offline') {
Row({ space: 12 }) {
Stack() {
Image(avatar)
.width(48)
.height(48)
.borderRadius(24)
Circle({ width: 12, height: 12 })
.fill(status === 'online' ? '#52C41A' : '#999')
.position({ x: 36, y: 36 })
}
Column({ space: 4 }) {
Text(name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(status === 'online' ? '在线' : '离线')
.fontSize(12)
.fontColor('#999')
}
.alignItems(HorizontalAlign.Start)
}
.padding(12)
.cardShadow() // 组合使用 @Styles
}
@Entry
@Component
struct ComparisonDemo {
build() {
Column({ space: 20 }) {
// 使用 @Styles
Column()
.width('100%')
.height(100)
.cardShadow()
// 使用 @Extend
Text('一级标题')
.themeText(1)
// 使用 @Builder
UserCard('张三', $r('app.media.avatar'), 'online')
}
.width('100%')
.padding(20)
}
}
五、组合使用最佳实践
5.1 分层设计
// 第一层:基础样式 (@Styles)
@Styles function baseCard() {
.backgroundColor(Color.White)
.borderRadius(12)
.padding(16)
}
@Styles function shadowSmall() {
.shadow({ radius: 4, color: '#00000010' })
}
@Styles function shadowLarge() {
.shadow({ radius: 12, color: '#00000030' })
}
// 第二层:组件扩展 (@Extend)
@Extend(Column) function card(elevation: 'small' | 'large') {
.baseCard()
if (elevation === 'small') {
.shadowSmall()
} else {
.shadowLarge()
}
}
@Extend(Text) function title(level: number) {
.fontSize(24 - level * 2)
.fontWeight(level === 1 ? FontWeight.Bold : FontWeight.Medium)
.fontColor('#333333')
}
// 第三层:UI结构 (@Builder)
@Builder function InfoCard(
titleText: string,
content: string,
elevation: 'small' | 'large'
) {
Column({ space: 12 }) {
Text(titleText)
.title(1)
.width('100%')
Text(content)
.fontSize(14)
.fontColor('#666666')
.width('100%')
}
.width('100%')
.card(elevation)
}
@Entry
@Component
struct LayeredDesignDemo {
build() {
Column({ space: 20 }) {
InfoCard('标题1', '这是一个小阴影卡片', 'small')
InfoCard('标题2', '这是一个大阴影卡片', 'large')
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F5F5')
}
}
5.2 复杂表单场景
// 样式定义
@Styles function inputContainerStyle() {
.width('100%')
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
// 组件扩展
@Extend(TextInput) function formInput(placeholder: string) {
.placeholder(placeholder)
.backgroundColor(Color.Transparent)
.fontSize(16)
.padding(0)
}
// UI 结构
@Builder function FormField(
label: string,
placeholder: string,
value: string,
onChange: (value: string) => void
) {
Column({ space: 8 }) {
Text(label)
.fontSize(14)
.fontColor('#666666')
.width('100%')
Column() {
TextInput({ text: value })
.formInput(placeholder)
.onChange(onChange)
}
.inputContainerStyle()
}
.width('100%')
}
@Entry
@Component
struct FormDemo {
@State username: string = ''
@State email: string = ''
@State phone: string = ''
build() {
Column({ space: 20 }) {
Text('用户注册')
.fontSize(24)
.fontWeight(FontWeight.Bold)
FormField('用户名', '请输入用户名', this.username, (value) => {
this.username = value
})
FormField('邮箱', '请输入邮箱', this.email, (value) => {
this.email = value
})
FormField('手机号', '请输入手机号', this.phone, (value) => {
this.phone = value
})
Button('提交')
.width('100%')
.height(48)
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor(Color.White)
}
}
六、性能优化建议
6.1 @Builder 的性能考虑
// ❌ 避免:每次都创建新的 Builder
@Entry
@Component
struct BadExample {
@State items: string[] = ['A', 'B', 'C']
build() {
Column() {
ForEach(this.items, (item: string) => {
// 每次都创建新的闭包,性能差
this.ItemBuilder(item, () => {
console.log(item)
})
})
}
}
@Builder ItemBuilder(text: string, onClick: () => void) {
Text(text).onClick(onClick)
}
}
// ✅ 推荐:复用 Builder
@Entry
@Component
struct GoodExample {
@State items: string[] = ['A', 'B', 'C']
handleItemClick(item: string) {
console.log(item)
}
@Builder ItemBuilder(text: string) {
Text(text)
.onClick(() => {
this.handleItemClick(text)
})
}
build() {
Column() {
ForEach(this.items, (item: string) => {
this.ItemBuilder(item)
})
}
}
}
6.2 合理拆分 Builder
// ✅ 将大的 Builder 拆分成小的独立部分
@Component
struct OptimizedList {
@State data: Array<any> = []
// 头部独立
@Builder HeaderBuilder() {
Row() {
Text('标题')
}
.height(56)
}
// 列表项独立
@Builder ListItemBuilder(item: any) {
Row() {
Text(item.name)
}
}
// 底部独立
@Builder FooterBuilder() {
Row() {
Text('已加载全部')
}
}
build() {
Column() {
this.HeaderBuilder()
List() {
ForEach(this.data, (item: any) => {
ListItem() {
this.ListItemBuilder(item)
}
})
}
this.FooterBuilder()
}
}
}
七、最佳实践总结
7.1 选择决策树
需要复用吗?
├─ 否 → 直接编写代码
└─ 是
├─ 只是样式属性?
│ ├─ 需要参数?
│ │ ├─ 是 → 使用 @Extend
│ │ └─ 否 → 使用 @Styles
│ └─ 针对特定组件? → 使用 @Extend
└─ 包含UI结构? → 使用 @Builder
7.2 命名规范
// @Styles: 描述性名称 + Style 后缀
@Styles function primaryButtonStyle() {}
@Styles function cardShadowStyle() {}
// @Extend: 组件类型 + 功能描述
@Extend(Button) function themeButton() {}
@Extend(Text) function headlineText() {}
// @Builder: 功能描述 + Builder/View 后缀
@Builder function UserCardBuilder() {}
@Builder function LoadingView() {}
7.3 注意事项
-
@Styles
- 只用于纯样式属性
- 全局定义使用
function,组件内不使用 - 不要尝试传递参数
-
@Extend
- 必须在组件外部定义
- 明确指定组件类型
- 合理使用参数,避免过多参数
-
@Builder
- 避免过于复杂的嵌套
- 合理拆分大型 Builder
- 注意状态管理和性能
-
组合使用
- @Styles 提供基础样式
- @Extend 提供参数化扩展
- @Builder 组织 UI 结构
- 保持清晰的分层
八、总结
| 装饰器 | 核心价值 | 典型场景 | 记忆口诀 |
|---|---|---|---|
| @Styles | 样式属性复用 | 固定的样式组合 | “样式打包” |
| @Extend | 组件样式参数化扩展 | 需要动态调整的样式 | “样式带参” |
| @Builder | UI 结构复用 | 复杂视图结构重复使用 | “结构模板” |
使用原则:
- 简单样式 → @Styles
- 参数化样式 → @Extend
- 结构复用 → @Builder
- 复杂场景 → 三者组合
掌握这三个装饰器的特点和使用场景,可以大幅提升 ArkTS 开发效率和代码质量!
更多推荐



所有评论(0)