项目演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

目录

  1. 引言:为什么需要公共样式复用
  2. @Styles 基础概念与设计哲学
  3. @Styles 语法详解
  4. 全局 @Styles 定义与使用
  5. 组件内 @Styles 定义与使用
  6. @Styles 限制条件深度解析
  7. 样式叠加与优先级机制
  8. 完整实战示例:@Styles 公共样式布局应用
  9. @Styles 与 @Extend 的对比与选型
  10. @Styles 与 AttributeModifier 的协作
  11. 多态样式 stateStyles 联用技巧
  12. 常见踩坑与排查方法
  13. 性能优化建议
  14. 最佳实践总结

1. 引言:为什么需要公共样式复用

1.1 开发痛点分析

在传统的 UI 开发中,开发者经常面临以下问题:

问题一:样式代码重复
// 传统写法:每个组件单独设置样式
Column() {
  Text('按钮1')
    .width(100)
    .height(40)
    .backgroundColor('#007DFF')
    .borderRadius(20)
  
  Text('按钮2')
    .width(100)
    .height(40)
    .backgroundColor('#007DFF')
    .borderRadius(20)
  
  Text('按钮3')
    .width(100)
    .height(40)
    .backgroundColor('#007DFF')
    .borderRadius(20)
}

上述代码中,三个按钮的样式完全相同,但需要重复编写三次。当按钮数量增加到数十个时,代码冗余问题将变得非常严重。

问题二:维护成本高

假设产品需求变更,需要将按钮的圆角从 20vp 改为 24vp,背景色从 #007DFF 改为 #005AFF。在传统写法下,开发者需要找到所有相关的按钮组件,逐一修改样式属性,不仅效率低下,还容易遗漏。

问题三:视觉一致性难以保障

在多人协作开发中,不同开发者对"标准按钮样式"的理解可能存在差异。有的开发者可能设置圆角为 16vp,有的设置为 20vp,导致应用界面风格不统一,影响用户体验。

1.2 @Styles 的解决方案

@Styles 装饰器正是为了解决上述问题而设计的。它允许开发者将重复的样式属性抽取为一个可复用的方法,从而实现:

目标 实现方式 收益
代码复用 将公共样式封装为 @Styles 方法 减少代码量,提高开发效率
统一规范 集中管理样式定义 确保视觉一致性
易于维护 修改一处定义,全局生效 降低维护成本,减少出错风险
提升性能 减少样式重复计算 优化渲染性能

2. @Styles 基础概念与设计哲学

2.1 @Styles 是什么

@Styles 是 ArkTS 提供的一种样式复用机制,通过装饰器将多条样式属性封装为一个可调用的方法。使用时只需在组件上链式调用该方法,即可快速应用预定义的样式组合。

2.2 设计哲学

原则一:关注点分离

将样式定义与组件使用分离,使代码结构更加清晰。开发者可以专注于业务逻辑,同时保持样式的统一性。

原则二:作用域隔离

@Styles 支持两种作用域:

  • 全局作用域:定义在组件外,可在整个文件中复用
  • 组件内作用域:定义在组件内,仅在当前组件内可用

这种设计避免了样式污染,同时提供了灵活的复用粒度。

原则三:渐进增强

多个 @Styles 可以叠加调用,后调用的样式会覆盖前面的相同属性。这种机制允许开发者在基础样式上进行定制,实现样式的渐进增强。

2.3 API 版本演进

@Styles 装饰器自 API Version 9 开始支持,后续版本不断增强:

API 版本 新增特性
API 9 基础功能支持,支持组件内和全局定义
API 9 支持 ArkTS 卡片
API 11 支持元服务
API 24 增强性能优化,完善类型检查

3. @Styles 语法详解

3.1 基本语法结构

@Styles 装饰器的基本语法如下:

// 全局 @Styles
@Styles
function styleName() {
  .property1(value1)
  .property2(value2)
  // ... 更多样式属性
}

// 组件内 @Styles
@Entry
@Component
struct ComponentName {
  @Styles
  styleName() {
    .property1(value1)
    .property2(value2)
    // ... 更多样式属性
  }
  
  build() {
    // 使用样式
    Text('示例')
      .styleName()
  }
}

3.2 关键语法要点

要点一:函数声明方式
  • 全局 @Styles:必须使用 function 关键字声明
  • 组件内 @Styles:不能使用 function 关键字
// 正确:全局 @Styles 使用 function 关键字
@Styles
function globalStyle() {
  .width(100)
}

// 正确:组件内 @Styles 不使用 function 关键字
@Entry
@Component
struct Index {
  @Styles
  componentStyle() {
    .width(100)
  }
}

// 错误:全局 @Styles 不能省略 function 关键字
@Styles
globalStyle() {  // 编译错误
  .width(100)
}
要点二:链式调用

@Styles 方法内部使用链式调用语法,每个样式属性以 . 开头:

@Styles
function cardStyle() {
  .width('90%')           // 宽度
  .padding(16)            // 内边距
  .borderRadius(12)       // 圆角
  .backgroundColor('#FFFFFF')  // 背景色
}
要点三:调用方式

在组件上通过链式调用应用 @Styles

Text('示例文本')
  .cardStyle()           // 应用样式
  .fontSize(16)          // 额外设置组件特有属性

4. 全局 @Styles 定义与使用

4.1 定义全局 @Styles

全局 @Styles 定义在所有组件外部,使用 function 关键字声明:

/**
 * 全局卡片基础样式
 * 适用场景:所有需要卡片效果的容器组件
 */
@Styles
function globalCardStyle() {
  .width('90%')
  .padding({ top: 16, right: 16, bottom: 16, left: 16 })
  .borderRadius(12)
  .backgroundColor('#FFFFFF')
  .shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
}

/**
 * 全局按钮样式
 * 适用场景:所有标准按钮
 */
@Styles
function globalButtonStyle() {
  .height(48)
  .borderRadius(24)
  .backgroundColor('#007DFF')
}

/**
 * 全局警告按钮样式
 * 适用场景:警告类型按钮,叠加在 globalButtonStyle 上使用
 */
@Styles
function globalWarningButtonStyle() {
  .backgroundColor('#FF6B35')
}

4.2 使用全局 @Styles

全局 @Styles 可以在文件内的任何组件中使用:

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {
      // 使用全局卡片样式
      Column() {
        Text('卡片内容')
      }
      .globalCardStyle()
      
      // 使用全局按钮样式
      Column() {
        Text('普通按钮')
      }
      .globalButtonStyle()
      .width('90%')
      
      // 使用全局按钮样式 + 警告样式(叠加)
      Column() {
        Text('警告按钮')
      }
      .globalButtonStyle()
      .globalWarningButtonStyle()
      .width('90%')
    }
  }
}

4.3 全局 @Styles 的特点

特点 说明
作用域 当前文件内全局可用
声明位置 所有组件外部
声明方式 必须使用 function 关键字
访问组件状态 不支持访问组件的 @State@Prop 等状态变量
事件处理 支持添加通用事件(如 onClick),但无法访问组件状态
优先级 低于组件内 @Styles

5. 组件内 @Styles 定义与使用

5.1 定义组件内 @Styles

组件内 @Styles 定义在组件内部,不使用 function 关键字:

@Entry
@Component
struct Index {
  @State count: number = 0
  @State isExpanded: boolean = false
  
  /**
   * 计数器卡片样式
   * 可以访问组件的状态变量
   */
  @Styles
  counterCardStyle() {
    .backgroundColor('#F0F5FF')
    .border({ width: 2, color: '#007DFF', style: BorderStyle.Solid })
  }
  
  /**
   * 动态高度样式
   * 根据状态变量动态调整样式
   */
  @Styles
  dynamicHeightStyle() {
    .height(this.isExpanded ? 200 : 100)
  }
  
  build() {
    Column() {
      // 使用组件内样式
      Column() {
        Text(`计数: ${this.count}`)
      }
      .counterCardStyle()
      
      // 使用动态样式
      Column() {
        Text('可展开区域')
      }
      .dynamicHeightStyle()
    }
  }
}

5.2 使用组件内 @Styles

组件内 @Styles 只能在当前组件的 build 方法中使用:

@Entry
@Component
struct Index {
  @State message: string = 'Hello'
  
  @Styles
  customStyle() {
    .width('80%')
    .padding(12)
    .backgroundColor('#E8F5E9')
  }
  
  build() {
    Column() {
      // 正确:在当前组件内使用
      Text(this.message)
        .customStyle()
    }
  }
}

// 错误:组件内 @Styles 不能在其他组件中使用
@Component
struct OtherComponent {
  build() {
    Column() {
      Text('其他组件')
        .customStyle()  // 编译错误:customStyle 未定义
    }
  }
}

5.3 组件内 @Styles 的特点

特点 说明
作用域 仅当前组件内可用
声明位置 组件内部,build 方法之前
声明方式 不使用 function 关键字
访问组件状态 可以访问组件的 @State@Prop@Link 等状态变量
事件处理 支持添加通用事件,且可以在事件回调中修改组件状态
优先级 高于全局 @Styles

5.4 组件内 @Styles 访问状态变量

组件内 @Styles 的一个重要特性是可以访问组件的状态变量,从而实现动态样式:

@Entry
@Component
struct DynamicStyleExample {
  @State heightValue: number = 50
  @State isHighlight: boolean = false
  
  @Styles
  dynamicCardStyle() {
    .height(this.heightValue)
    .backgroundColor(this.isHighlight ? '#FFEB3B' : '#FFFFFF')
    .onClick(() => {
      this.heightValue += 20
      this.isHighlight = !this.isHighlight
    })
  }
  
  build() {
    Column() {
      Text('点击改变样式')
        .dynamicCardStyle()
        .width('80%')
    }
  }
}

上述示例中,dynamicCardStyle 访问了组件的 heightValueisHighlight 状态变量,并在点击事件中修改了这些变量,从而实现了样式的动态变化。


6. @Styles 限制条件深度解析

6.1 仅支持通用属性

核心限制@Styles 方法内只能包含通用属性通用事件,不能包含组件特有属性。

通用属性列表(部分)
属性类别 示例
尺寸属性 widthheightminWidthminHeightmaxWidthmaxHeight
内边距 paddingpaddingToppaddingRightpaddingBottompaddingLeft
外边距 marginmarginTopmarginRightmarginBottommarginLeft
背景 backgroundColorbackgroundImagebackgroundSize
边框 borderborderWidthborderColorborderStyleborderRadius
阴影 shadow
透明度 opacity
对齐 align(部分组件)
组件特有属性(不能放入 @Styles)
属性类别 示例 所属组件
字体属性 fontSizefontColorfontWeightfontFamily Text
文本属性 textAlignmaxLineslineHeight Text
输入属性 placeholderinputTypemaxLength TextInput
图片属性 objectFitinterpolation Image
布局属性 justifyContentalignItemslayoutWeight Column、Row
错误示例
// 错误:fontSize 是 Text 组件特有属性,不能放入 @Styles
@Styles
function textStyle() {
  .fontSize(16)      // 编译错误
  .fontColor('#333') // 编译错误
}

// 错误:justifyContent 是容器布局属性,不能放入 @Styles
@Styles
function flexCenterStyle() {
  .justifyContent(FlexAlign.Center)  // 编译错误
  .alignItems(HorizontalAlign.Center) // 编译错误
}

// 正确:只包含通用属性
@Styles
function cardStyle() {
  .width('90%')
  .padding(16)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
}

6.2 不支持参数传递

核心限制@Styles 方法不支持传入参数,所有样式值必须在方法内硬编码或通过组件状态变量获取。

错误示例
// 错误:@Styles 不支持参数
@Styles
function customWidthStyle(widthValue: number) {
  .width(widthValue)  // 编译错误
}

// 错误:同样不支持参数
@Styles
function coloredBox(color: string) {
  .backgroundColor(color)  // 编译错误
}
正确解决方案

方案一:定义多个 @Styles 方法

// 定义不同宽度的样式
@Styles
function widthSmall() {
  .width(100)
}

@Styles
function widthMedium() {
  .width(200)
}

@Styles
function widthLarge() {
  .width(300)
}

方案二:使用组件内 @Styles 访问状态变量

@Entry
@Component
struct DynamicWidthExample {
  @State boxWidth: number = 150
  
  @Styles
  dynamicWidthStyle() {
    .width(this.boxWidth)
    .height(100)
    .backgroundColor('#007DFF')
  }
  
  build() {
    Column() {
      Text('动态宽度')
        .dynamicWidthStyle()
      
      Button('增加宽度')
        .onClick(() => {
          this.boxWidth += 50
        })
    }
  }
}

方案三:使用 @Extend(支持参数传递)

// @Extend 支持参数传递
@Extend(Column)
function customWidth(widthValue: number) {
  .width(widthValue)
  .height(100)
}

@Entry
@Component
struct ExtendExample {
  build() {
    Column() {
      Text('宽度 100')
    }
    .customWidth(100)
    
    Column() {
      Text('宽度 200')
    }
    .customWidth(200)
  }
}

6.3 不支持逻辑组件

核心限制@Styles 方法内不能使用逻辑组件(如 ifforLazyForEach)。

错误示例
// 错误:@Styles 内不能使用 if 逻辑
@Styles
function conditionalStyle() {
  if (true) {
    .backgroundColor('#FF0000')  // 编译错误
  } else {
    .backgroundColor('#00FF00')  // 编译错误
  }
}

// 错误:@Styles 内不能使用三元表达式
@Styles
function ternaryStyle() {
  .backgroundColor(true ? '#FF0000' : '#00FF00')  // 编译错误
}
正确解决方案

将逻辑判断移到组件使用处:

@Styles
function redBackground() {
  .backgroundColor('#FF0000')
}

@Styles
function greenBackground() {
  .backgroundColor('#00FF00')
}

@Entry
@Component
struct ConditionalStyleExample {
  @State isError: boolean = true
  
  build() {
    Column() {
      Text('条件样式')
        .width(200)
        .height(100)
        // 在组件使用处进行条件判断
        .apply(() => {
          if (this.isError) {
            return redBackground
          } else {
            return greenBackground
          }
        })
    }
  }
}

或者直接在组件上使用条件属性:

@Entry
@Component
struct ConditionalStyleExample {
  @State isError: boolean = true
  
  build() {
    Column() {
      Text('条件样式')
        .width(200)
        .height(100)
        .backgroundColor(this.isError ? '#FF0000' : '#00FF00')
    }
  }
}

6.4 不支持 export

核心限制@Styles 方法只能在当前文件内使用,不支持通过 export 导出到其他文件。

错误示例
// styles.ets 文件
@Styles
export function globalCardStyle() {  // 编译错误:@Styles 不支持 export
  .width('90%')
  .backgroundColor('#FFFFFF')
}

// index.ets 文件
import { globalCardStyle } from './styles'  // 编译错误:无法导入
正确解决方案

方案一:使用 @Extend(支持 export)

// styles.ets 文件
@Extend(Column)
export function cardStyle() {
  .width('90%')
  .padding(16)
  .backgroundColor('#FFFFFF')
}

// index.ets 文件
import { cardStyle } from './styles'

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('卡片内容')
    }
    .cardStyle()
  }
}

方案二:使用 AttributeModifier(支持 export)

// styles.ets 文件
export class CardModifier extends AttributeModifier<ColumnAttribute> {
  applyNormalAttribute(instance: ColumnAttribute): void {
    instance.width('90%')
    instance.padding(16)
    instance.backgroundColor('#FFFFFF')
  }
}

// index.ets 文件
import { CardModifier } from './styles'

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('卡片内容')
    }
    .applyModifier(new CardModifier())
  }
}

6.5 文件级作用域

核心限制@Styles 的作用域仅限于当前文件,即使是全局 @Styles 也不能跨文件使用。

示例说明
// file1.ets
@Styles
function commonStyle() {
  .width(100)
  .height(50)
}

@Entry
@Component
struct Page1 {
  build() {
    Column() {
      Text('Page1')
        .commonStyle()  // 正确:同一文件内可用
    }
  }
}

// file2.ets
@Component
struct Page2 {
  build() {
    Column() {
      Text('Page2')
        .commonStyle()  // 编译错误:commonStyle 未定义
    }
  }
}

7. 样式叠加与优先级机制

7.1 样式叠加规则

多个 @Styles 可以叠加调用,后调用的样式会覆盖前面的相同属性:

@Styles
function baseStyle() {
  .width(200)
  .height(100)
  .backgroundColor('#FFFFFF')
  .borderRadius(8)
}

@Styles
function accentStyle() {
  .backgroundColor('#007DFF')  // 覆盖背景色
  .borderRadius(16)            // 覆盖圆角
}

@Entry
@Component
struct StyleOverlayExample {
  build() {
    Column() {
      Text('叠加效果')
    }
    .baseStyle()       // 应用基础样式
    .accentStyle()     // 叠加强调样式,覆盖背景色和圆角
    // 最终样式:width=200, height=100, backgroundColor='#007DFF', borderRadius=16
  }
}

7.2 优先级顺序

样式的优先级从低到高为:

  1. 全局 @Styles:定义在组件外的样式
  2. 组件内 @Styles:定义在组件内的样式
  3. 组件直接设置的属性:在组件上直接链式调用的属性
@Styles
function globalStyle() {
  .width(100)
  .height(50)
  .backgroundColor('#FF0000')
}

@Entry
@Component
struct PriorityExample {
  @Styles
  componentStyle() {
    .width(150)           // 覆盖全局样式的 width
    .backgroundColor('#00FF00')  // 覆盖全局样式的 backgroundColor
  }
  
  build() {
    Column() {
      Text('优先级示例')
    }
    .globalStyle()        // 优先级最低
    .componentStyle()     // 优先级中等,覆盖全局样式
    .width(200)           // 优先级最高,覆盖所有样式
    // 最终样式:width=200, height=50, backgroundColor='#00FF00'
  }
}

7.3 叠加示例

示例一:基础样式 + 主题样式
@Styles
function baseCard() {
  .width('90%')
  .padding(16)
  .borderRadius(12)
}

@Styles
function primaryTheme() {
  .backgroundColor('#E3F2FD')
  .border({ width: 1, color: '#1976D2', style: BorderStyle.Solid })
}

@Styles
function successTheme() {
  .backgroundColor('#E8F5E9')
  .border({ width: 1, color: '#388E3C', style: BorderStyle.Solid })
}

@Entry
@Component
struct ThemeOverlayExample {
  build() {
    Column({ space: 16 }) {
      // 基础卡片 + 主主题
      Column() {
        Text('主主题卡片')
      }
      .baseCard()
      .primaryTheme()
      
      // 基础卡片 + 成功主题
      Column() {
        Text('成功主题卡片')
      }
      .baseCard()
      .successTheme()
    }
  }
}
示例二:多层叠加
@Styles
function level1() {
  .width(100)
  .height(100)
  .backgroundColor('#FFFFFF')
}

@Styles
function level2() {
  .height(150)           // 覆盖 height
  .borderRadius(8)
}

@Styles
function level3() {
  .width(200)            // 覆盖 width
  .backgroundColor('#007DFF')  // 覆盖 backgroundColor
}

@Entry
@Component
struct MultiLevelOverlay {
  build() {
    Column() {
      Text('多层叠加')
    }
    .level1()
    .level2()
    .level3()
    // 最终样式:width=200, height=150, backgroundColor='#007DFF', borderRadius=8
  }
}

8. 完整实战示例:@Styles 公共样式布局应用

8.1 需求分析

本示例展示一个完整的 @Styles 公共样式布局应用,包含以下功能:

  1. 全局样式演示:展示全局 @Styles 的定义和使用
  2. 组件内样式演示:展示组件内 @Styles 的定义和使用,包括访问状态变量
  3. 样式叠加演示:展示多个 @Styles 的叠加效果
  4. 按钮样式复用:展示按钮样式的复用和变体
  5. 动态样式演示:展示基于状态变量的动态样式

8.2 完整代码

/**
 * @Styles 公共样式布局示例(API 24)
 * 
 * 核心技术点:
 * 1. 全局 @Styles:在组件外定义,可在整个文件中复用
 * 2. 组件内 @Styles:在组件内定义,仅在该组件内可用
 * 3. @Styles 仅支持通用属性(width/height/padding/backgroundColor/borderRadius/border/shadow/margin 等)
 * 4. 多个 @Styles 可叠加调用,后调用的样式会覆盖前面的相同属性
 */

// ==================== 全局 @Styles 定义 ====================

/**
 * 全局卡片基础样式
 * 包含:宽度、内边距、圆角、背景色、阴影效果(均为通用属性)
 */
@Styles
function globalCardStyle() {
  .width('90%')
  .padding({ top: 16, right: 16, bottom: 16, left: 16 })
  .borderRadius(12)
  .backgroundColor('#FFFFFF')
  .shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
}

/**
 * 全局按钮样式
 * 包含:高度、圆角、背景色(均为通用属性)
 * 注意:justifyContent/alignItems 是容器布局属性,不能放在 @Styles 中
 */
@Styles
function globalButtonStyle() {
  .height(48)
  .borderRadius(24)
  .backgroundColor('#007DFF')
}

/**
 * 全局警告按钮样式
 * 包含:警告主题背景色(通用属性)
 * 用于叠加覆盖全局按钮样式中的背景色
 */
@Styles
function globalWarningButtonStyle() {
  .backgroundColor('#FF6B35')
}

/**
 * 全局虚线边框样式
 * 包含:虚线边框、内边距(通用属性)
 */
@Styles
function globalDashedBorderStyle() {
  .border({ width: 2, color: '#007DFF', style: BorderStyle.Dashed })
  .padding({ top: 12, right: 12, bottom: 12, left: 12 })
}

// ==================== 组件定义 ====================

@Entry
@Component
struct Index {
  // 组件内状态管理
  @State message: string = '点击按钮查看效果';
  @State count: number = 0;
  @State isDarkMode: boolean = false;

  /**
   * 组件内 @Styles 定义 - 仅在当前组件内可用
   * 计数器卡片样式:自定义背景色和边框(通用属性)
   */
  @Styles
  componentCounterCardStyle() {
    .backgroundColor('#F0F5FF')
    .border({ width: 2, color: '#007DFF', style: BorderStyle.Solid })
  }

  /**
   * 组件内 @Styles 定义 - 仅在当前组件内可用
   * 消息卡片样式:自定义背景色(通用属性)
   */
  @Styles
  componentMessageCardStyle() {
    .backgroundColor('#E8F5E9')
  }

  /**
   * 组件内 @Styles 定义 - 仅在当前组件内可用
   * 紧凑卡片样式:较小的内边距(通用属性)
   */
  @Styles
  componentCompactCardStyle() {
    .padding({ top: 10, right: 10, bottom: 10, left: 10 })
    .borderRadius(8)
  }

  /**
   * 组件内 @Styles 定义 - 仅在当前组件内可用
   * 动态背景样式:根据状态变量切换背景色
   */
  @Styles
  componentDynamicBackgroundStyle() {
    .backgroundColor(this.isDarkMode ? '#2C2C2C' : '#FFFFFF')
  }

  build() {
    // 主容器:垂直布局,占满屏幕高度
    Column({ space: 20 }) {
      // ==================== 标题区域 ====================
      Text('@Styles 公共样式布局示例')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1A1A1A')
        .margin({ top: 32 })

      // ==================== 全局 @Styles 演示 ====================
      Column({ space: 12 }) {
        Text('全局 @Styles 演示')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        // 使用全局卡片样式
        Column({ space: 8 }) {
          Text('这是一个使用全局样式的卡片')
            .fontSize(16)
            .fontColor('#333333')
            .fontWeight(FontWeight.Medium)

          Text('全局样式特点:\n1. 在组件外定义\n2. 可在整个文件中复用\n3. 仅支持通用属性(width/height/padding/backgroundColor/borderRadius/border/shadow/margin 等)')
            .fontSize(14)
            .fontColor('#666666')
            .textAlign(TextAlign.Start)
        }
        .globalCardStyle() // 应用全局卡片样式
      }

      // ==================== 组件内 @Styles 演示 ====================
      Column({ space: 12 }) {
        Text('组件内 @Styles 演示')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        // 使用全局卡片样式 + 组件内计数器卡片样式(样式叠加)
        Column({ space: 8 }) {
          Text('计数器卡片')
            .fontSize(16)
            .fontColor('#333333')
            .fontWeight(FontWeight.Medium)

          Text(`${this.count}`)
            .fontSize(48)
            .fontColor('#00B42A')
            .fontWeight(FontWeight.Bold)

          Text('组件内样式特点:\n1. 在组件内定义\n2. 仅在当前组件内可用\n3. 可与全局样式叠加使用\n4. 可以访问组件的状态变量')
            .fontSize(14)
            .fontColor('#666666')
            .textAlign(TextAlign.Start)
        }
        .globalCardStyle() // 应用全局卡片样式(基础样式)
        .componentCounterCardStyle() // 叠加组件内计数器卡片样式(覆盖背景色,添加边框)
      }

      // ==================== 样式叠加演示 ====================
      Column({ space: 12 }) {
        Text('样式叠加演示')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        // 使用全局卡片样式 + 全局虚线边框样式(样式叠加)
        Column({ space: 8 }) {
          Text('样式叠加效果')
            .fontSize(16)
            .fontColor('#333333')
            .fontWeight(FontWeight.Medium)

          Text('多个 @Styles 可叠加调用,后调用的样式会覆盖前面的相同属性')
            .fontSize(14)
            .fontColor('#666666')
            .textAlign(TextAlign.Start)
        }
        .globalCardStyle() // 基础样式
        .globalDashedBorderStyle() // 叠加虚线边框样式
      }

      // ==================== 按钮样式演示 ====================
      Column({ space: 12 }) {
        Text('按钮样式复用演示')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        // 使用全局按钮样式的按钮
        Column() {
          Text('普通按钮')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Medium)
        }
        .globalButtonStyle() // 应用全局按钮样式
        .width('90%')
        .justifyContent(FlexAlign.Center) // 布局属性直接设置
        .alignItems(HorizontalAlign.Center) // 布局属性直接设置
        .onClick(() => {
          this.message = '普通按钮被点击!';
        })

        // 使用全局按钮样式 + 全局警告按钮样式(样式叠加)
        Column() {
          Text('警告按钮')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Medium)
        }
        .globalButtonStyle() // 应用全局按钮样式(基础样式)
        .globalWarningButtonStyle() // 叠加警告按钮样式(覆盖背景色)
        .width('90%')
        .justifyContent(FlexAlign.Center) // 布局属性直接设置
        .alignItems(HorizontalAlign.Center) // 布局属性直接设置
        .onClick(() => {
          this.message = '警告按钮被点击!';
        })

        // 使用全局按钮样式的计数器按钮
        Column() {
          Text(`点击计数: ${this.count}`)
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Medium)
        }
        .globalButtonStyle() // 应用全局按钮样式
        .width('90%')
        .justifyContent(FlexAlign.Center) // 布局属性直接设置
        .alignItems(HorizontalAlign.Center) // 布局属性直接设置
        .onClick(() => {
          this.count++;
          this.message = `计数器: ${this.count}`;
        })
      }

      // ==================== 动态样式演示 ====================
      Column({ space: 12 }) {
        Text('动态样式演示')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        // 使用全局卡片样式 + 组件内动态背景样式
        Column({ space: 8 }) {
          Text(`深色模式: ${this.isDarkMode ? '开启' : '关闭'}`)
            .fontSize(16)
            .fontColor(this.isDarkMode ? '#FFFFFF' : '#333333')
            .fontWeight(FontWeight.Medium)
        }
        .globalCardStyle() // 基础样式
        .componentDynamicBackgroundStyle() // 动态背景样式

        // 切换深色模式按钮
        Column() {
          Text(this.isDarkMode ? '切换到浅色模式' : '切换到深色模式')
            .fontSize(16)
            .fontColor('#FFFFFF')
            .fontWeight(FontWeight.Medium)
        }
        .globalButtonStyle()
        .width('90%')
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .onClick(() => {
          this.isDarkMode = !this.isDarkMode;
          this.message = `深色模式已${this.isDarkMode ? '开启' : '关闭'}`;
        })
      }

      // ==================== 消息展示区域 ====================
      Column({ space: 12 }) {
        Text('消息提示')
          .fontSize(20)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')

        // 使用全局卡片样式 + 组件内消息卡片样式(样式叠加)
        Column({ space: 8 }) {
          Text(this.message)
            .fontSize(16)
            .fontColor('#007DFF')
            .fontWeight(FontWeight.Medium)
        }
        .globalCardStyle() // 基础样式
        .componentMessageCardStyle() // 叠加消息卡片样式(覆盖背景色)
        .componentCompactCardStyle() // 叠加紧凑卡片样式(覆盖内边距和圆角)
      }

    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F7FA')
    .padding({ bottom: 32 })
  }
}

8.3 代码解析

全局 @Styles 定义
@Styles
function globalCardStyle() {
  .width('90%')
  .padding({ top: 16, right: 16, bottom: 16, left: 16 })
  .borderRadius(12)
  .backgroundColor('#FFFFFF')
  .shadow({ radius: 8, color: '#0000001A', offsetY: 4 })
}

该样式定义了卡片的基础样式,包含宽度、内边距、圆角、背景色和阴影效果,这些都是通用属性,可以在任何组件上使用。

组件内 @Styles 定义
@Styles
componentDynamicBackgroundStyle() {
  .backgroundColor(this.isDarkMode ? '#2C2C2C' : '#FFFFFF')
}

该样式访问了组件的 isDarkMode 状态变量,根据状态值动态切换背景色,体现了组件内 @Styles 的核心优势——可以访问组件状态。

样式叠加使用
Column() {
  Text('消息内容')
}
.globalCardStyle()           // 基础样式
.componentMessageCardStyle() // 覆盖背景色
.componentCompactCardStyle() // 覆盖内边距和圆角

通过叠加多个 @Styles,实现了基础样式 + 主题样式 + 紧凑样式的组合效果。

布局属性的正确使用
Column() {
  Text('按钮文本')
}
.globalButtonStyle()          // 应用按钮样式
.width('90%')
.justifyContent(FlexAlign.Center)  // 布局属性直接设置
.alignItems(HorizontalAlign.Center) // 布局属性直接设置

注意 justifyContentalignItems 是布局属性,不能放入 @Styles 中,需要在组件上直接设置。


9. @Styles 与 @Extend 的对比与选型

9.1 核心区别

特性 @Styles @Extend
定义方式 使用 function 关键字(全局)或直接定义(组件内) 使用 @Extend(Component) 语法
作用域 文件级作用域,不支持 export 支持 export,可跨文件使用
参数传递 不支持 支持
属性限制 仅支持通用属性和通用事件 支持通用属性和组件特有属性
访问组件状态 组件内 @Styles 支持 不支持
适用场景 单个文件内的样式复用 跨文件的样式复用、参数化样式

9.2 @Extend 语法示例

// 定义扩展样式(支持参数)
@Extend(Text)
function fancyText(fontSizeValue: number, fontColorValue: string) {
  .fontSize(fontSizeValue)
  .fontColor(fontColorValue)
  .fontWeight(FontWeight.Bold)
  .textAlign(TextAlign.Center)
}

@Extend(Column)
function cardWithTheme(themeColor: string) {
  .width('90%')
  .padding(16)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .border({ width: 2, color: themeColor, style: BorderStyle.Solid })
}

@Entry
@Component
struct ExtendExample {
  build() {
    Column({ space: 16 }) {
      // 使用扩展样式(带参数)
      Text('大标题')
        .fancyText(24, '#1A1A1A')
      
      Text('小标题')
        .fancyText(18, '#666666')
      
      // 使用带主题的卡片样式
      Column() {
        Text('蓝色主题卡片')
      }
      .cardWithTheme('#007DFF')
      
      Column() {
        Text('绿色主题卡片')
      }
      .cardWithTheme('#00B42A')
    }
  }
}

9.3 选型建议

场景 推荐方案 理由
单文件内样式复用 @Styles 简单直接,无需导出
需要访问组件状态 组件内 @Styles 可以访问 @State、@Prop 等状态变量
跨文件样式复用 @Extend 支持 export,可在多个文件中复用
参数化样式 @Extend 支持参数传递,实现灵活定制
组件特有属性复用 @Extend @Styles 不支持组件特有属性
简单样式组合 @Styles 语法更简洁

10. @Styles 与 AttributeModifier 的协作

10.1 AttributeModifier 概述

AttributeModifier 是一种更灵活的样式扩展机制,允许开发者通过类的方式定义样式,并支持动态修改和状态响应。

10.2 AttributeModifier 语法示例

// 定义属性修饰器类
class CardModifier extends AttributeModifier<ColumnAttribute> {
  private backgroundColor: string = '#FFFFFF'
  
  constructor(bgColor: string) {
    super()
    this.backgroundColor = bgColor
  }
  
  applyNormalAttribute(instance: ColumnAttribute): void {
    instance.width('90%')
    instance.padding(16)
    instance.backgroundColor(this.backgroundColor)
    instance.borderRadius(12)
  }
}

@Entry
@Component
struct AttributeModifierExample {
  @State cardColor: string = '#E3F2FD'
  
  build() {
    Column() {
      Text('使用 AttributeModifier')
    }
    .applyModifier(new CardModifier(this.cardColor))
  }
}

10.3 三者对比

特性 @Styles @Extend AttributeModifier
定义方式 函数式 扩展式 类式
参数传递 不支持 支持 支持(通过构造函数)
作用域 文件级 支持 export 支持 export
动态性 有限(组件内可访问状态) 有限 高(可动态修改)
状态响应 组件内 @Styles 支持 不支持 支持
复杂度
适用场景 简单样式复用 参数化样式 复杂动态样式

10.4 协作使用示例

// 定义全局 @Styles
@Styles
function baseCardStyle() {
  .width('90%')
  .padding(16)
  .borderRadius(12)
}

// 定义 AttributeModifier
class ThemeModifier extends AttributeModifier<ColumnAttribute> {
  constructor(private theme: 'primary' | 'success' | 'warning') {
    super()
  }
  
  applyNormalAttribute(instance: ColumnAttribute): void {
    const colors = {
      primary: '#E3F2FD',
      success: '#E8F5E9',
      warning: '#FFF3E0'
    }
    instance.backgroundColor(colors[this.theme])
  }
}

@Entry
@Component
struct CombinedUsage {
  @State currentTheme: 'primary' | 'success' | 'warning' = 'primary'
  
  build() {
    Column({ space: 16 }) {
      // 组合使用 @Styles 和 AttributeModifier
      Column() {
        Text('组合样式')
      }
      .baseCardStyle()                    // 使用 @Styles 设置基础样式
      .applyModifier(new ThemeModifier(this.currentTheme)) // 使用 AttributeModifier 设置动态主题
    }
  }
}

11. 多态样式 stateStyles 联用技巧

11.1 stateStyles 概述

stateStyles 用于设置组件不同状态下的样式,支持以下状态:

  • normal:组件无状态时的样式
  • pressed:组件按下状态的样式
  • disabled:组件禁用状态的样式
  • focused:组件获焦状态的样式
  • clicked:组件点击状态的样式

11.2 与 @Styles 联用示例

// 定义不同状态的样式
@Styles
function normalStyle() {
  .backgroundColor('#007DFF')
  .borderRadius(24)
}

@Styles
function pressedStyle() {
  .backgroundColor('#005AFF')
  .borderRadius(24)
}

@Styles
function disabledStyle() {
  .backgroundColor('#BDBDBD')
  .borderRadius(24)
}

@Entry
@Component
struct StateStylesExample {
  @State isEnabled: boolean = true
  
  build() {
    Column() {
      Text('多态样式按钮')
        .fontSize(16)
        .fontColor('#FFFFFF')
        .width(200)
        .height(48)
        .stateStyles({
          normal: normalStyle,
          pressed: pressedStyle,
          disabled: disabledStyle
        })
        .enabled(this.isEnabled)
    }
  }
}

11.3 注意事项

  1. stateStyles 仅支持通用属性:与 @Styles 一样,stateStyles 中也只能包含通用属性和通用事件。

  2. Builder 中的多态样式:由于 Builder 不具备独立的自定义父节点,多态样式无法直接在 Builder 中生效。解决方法是将多态样式封装至自定义组件内部,再将此组件置于 @Builder 中。

  3. 焦点态生效条件:多态样式的焦点态只有在焦点激活态开启时生效。


12. 常见踩坑与排查方法

12.1 坑一:组件特有属性放入 @Styles

问题描述:将 fontSizefontColorfontWeight 等组件特有属性放入 @Styles 中,导致编译错误。

错误代码

@Styles
function textStyle() {
  .fontSize(16)      // 编译错误:fontSize 是 Text 组件特有属性
  .fontColor('#333') // 编译错误
}

解决方案:将组件特有属性直接在组件上设置:

@Styles
function commonContainerStyle() {
  .width('90%')
  .padding(16)
}

Text('示例')
  .commonContainerStyle()
  .fontSize(16)      // 直接在组件上设置
  .fontColor('#333')

12.2 坑二:布局属性放入 @Styles

问题描述:将 justifyContentalignItems 等布局属性放入 @Styles 中,导致编译错误。

错误代码

@Styles
function flexCenterStyle() {
  .justifyContent(FlexAlign.Center)  // 编译错误
  .alignItems(HorizontalAlign.Center)
}

解决方案:将布局属性直接在容器组件上设置:

@Styles
function containerStyle() {
  .width('100%')
  .height(100)
}

Column() {
  Text('居中内容')
}
.containerStyle()
.justifyContent(FlexAlign.Center)  // 直接设置
.alignItems(HorizontalAlign.Center)

12.3 坑三:FontWeight.SemiBold 不存在

问题描述:使用 FontWeight.SemiBold 导致编译错误,提示属性不存在。

错误代码

Text('示例')
  .fontWeight(FontWeight.SemiBold)  // 编译错误

解决方案:使用有效的 FontWeight 值:

Text('示例')
  .fontWeight(FontWeight.Medium)  // 正确
// 或
Text('示例')
  .fontWeight(500)  // 正确,使用数值

12.4 坑四:@Styles 跨文件使用

问题描述:尝试在一个文件中使用另一个文件定义的 @Styles,导致编译错误。

错误代码

// styles.ets
@Styles
function cardStyle() {
  .width('90%')
}

// index.ets
@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('卡片')
        .cardStyle()  // 编译错误:cardStyle 未定义
    }
  }
}

解决方案:使用 @ExtendAttributeModifier 实现跨文件样式复用:

// styles.ets
@Extend(Column)
export function cardStyle() {
  .width('90%')
  .padding(16)
}

// index.ets
import { cardStyle } from './styles'

@Entry
@Component
struct Index {
  build() {
    Column() {
      Text('卡片')
    }
    .cardStyle()  // 正确
  }
}

12.5 坑五:@Styles 传入参数

问题描述:尝试为 @Styles 方法传入参数,导致编译错误。

错误代码

@Styles
function customWidth(widthValue: number) {  // 编译错误
  .width(widthValue)
}

解决方案:使用组件内 @Styles 访问状态变量,或使用 @Extend

// 方案一:组件内 @Styles
@Entry
@Component
struct Example {
  @State widthValue: number = 100
  
  @Styles
  dynamicWidthStyle() {
    .width(this.widthValue)
  }
}

// 方案二:@Extend
@Extend(Column)
function customWidth(widthValue: number) {
  .width(widthValue)
}

12.6 坑六:@Styles 内使用逻辑组件

问题描述:在 @Styles 方法内使用 iffor 等逻辑,导致编译错误。

错误代码

@Styles
function conditionalStyle() {
  if (true) {
    .backgroundColor('#FF0000')  // 编译错误
  }
}

解决方案:将逻辑判断移到组件使用处:

@Styles
function redStyle() {
  .backgroundColor('#FF0000')
}

@Styles
function greenStyle() {
  .backgroundColor('#00FF00')
}

@Entry
@Component
struct Example {
  @State isError: boolean = true
  
  build() {
    Column() {
      Text('条件样式')
    }
    .apply(() => {
      return this.isError ? redStyle : greenStyle
    })
  }
}

13. 性能优化建议

13.1 减少 @Styles 方法数量

虽然 @Styles 可以提高代码复用性,但过多的 @Styles 方法会增加样式查找的开销。建议将相关的样式合并为较少的方法。

优化前

@Styles
function style1() {
  .width(100)
}

@Styles
function style2() {
  .height(50)
}

@Styles
function style3() {
  .backgroundColor('#FFFFFF')
}

Column() {
  Text('示例')
}
.style1()
.style2()
.style3()

优化后

@Styles
function combinedStyle() {
  .width(100)
  .height(50)
  .backgroundColor('#FFFFFF')
}

Column() {
  Text('示例')
}
.combinedStyle()

13.2 避免过度叠加

过多的 @Styles 叠加会增加样式计算的复杂度,建议控制叠加层数在 3 层以内。

优化前

Column() {
  Text('示例')
}
.style1()
.style2()
.style3()
.style4()
.style5()  // 过多叠加

优化后

@Styles
function baseStyle() {
  .style1()
  .style2()
  .style3()
}

Column() {
  Text('示例')
}
.baseStyle()
.style4()  // 控制在 3 层以内

13.3 使用资源文件管理颜色和尺寸

将颜色、尺寸等常量定义在资源文件中,避免在 @Styles 中硬编码。

优化前

@Styles
function cardStyle() {
  .backgroundColor('#FFFFFF')  // 硬编码
  .borderRadius(12)            // 硬编码
}

优化后

@Styles
function cardStyle() {
  .backgroundColor($r('app.color.card_background'))
  .borderRadius($r('app.float.card_radius'))
}

13.4 避免在 @Styles 中使用复杂计算

复杂的计算逻辑会在每次样式应用时执行,影响性能。

优化前

@Styles
function dynamicStyle() {
  .width(Math.sqrt(100) * 2)  // 复杂计算
}

优化后

const calculatedWidth = Math.sqrt(100) * 2

@Styles
function dynamicStyle() {
  .width(calculatedWidth)  // 预先计算
}

14. 最佳实践总结

14.1 样式组织规范

├── src/
│   ├── main/
│   │   ├── ets/
│   │   │   ├── pages/
│   │   │   │   ├── Index.ets           # 页面组件
│   │   │   │   └── OtherPage.ets       # 其他页面
│   │   │   ├── components/
│   │   │   │   └── CustomComponent.ets # 自定义组件
│   │   │   └── styles/
│   │   │       └── CommonStyles.ets    # @Extend 样式(跨文件复用)
│   │   └── resources/
│   │       ├── base/
│   │       │   ├── color/
│   │       │   │   └── color.json      # 颜色资源
│   │       │   └── float/
│   │       │       └── float.json      # 尺寸资源

14.2 命名规范

类型 命名规则 示例
全局 @Styles global + 描述 + Style globalCardStyle
组件内 @Styles component + 描述 + Style componentCounterCardStyle
@Extend 描述 + 组件名 cardStyle(扩展 Column)

14.3 使用建议

  1. 单文件内样式复用:使用 @Styles,简单直接。
  2. 需要访问组件状态:使用组件内 @Styles
  3. 跨文件样式复用:使用 @Extend
  4. 参数化样式:使用 @Extend
  5. 复杂动态样式:使用 AttributeModifier
  6. 多状态样式:使用 stateStyles 配合 @Styles

14.4 注意事项

  1. 仅使用通用属性@Styles 内只能包含通用属性和通用事件。
  2. 避免参数传递@Styles 不支持参数传递。
  3. 避免逻辑组件@Styles 内不能使用 iffor 等逻辑。
  4. 文件级作用域@Styles 只能在当前文件内使用。
  5. 优先级顺序:组件内 @Styles > 全局 @Styles > 组件直接属性。

结语

@Styles 装饰器是 HarmonyOS ArkTS 中实现样式复用的重要机制,通过合理使用 @Styles,可以显著提高代码的可维护性和开发效率。在实际开发中,应根据具体场景选择合适的样式复用方案,并遵循相关的限制条件和最佳实践,以确保代码的质量和性能。

希望本文能帮助开发者深入理解 @Styles 的使用方法和设计思想,在实际项目中灵活运用,构建出优雅、高效的 UI 代码。


参考资料

  1. HarmonyOS 官方文档 - @Styles装饰器:定义组件重用样式
  2. HarmonyOS 官方文档 - 通用属性
  3. HarmonyOS 官方文档 - 多态样式
  4. HarmonyOS 官方文档 - AttributeModifier
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐