鸿蒙 ArkUI 进阶:@Extend 和 @Styles,把样式也抽成「乐高块」的正确姿势

鸿蒙 ArkUI 进阶:@Extend 和 @Styles,把样式也抽成「乐高块」的正确姿势

写在前面

如果你写 ArkUI 写过三个页面以上,大概率遇到过这个场景:

设计稿里所有卡片都用 #fff 背景 + 10 圆角 + 14 内边距 + 一点阴影。你第一张卡片写了四个属性,第二张复制粘贴,第三张......写到第十张你发现圆角设计改成了 12,你改了十遍,漏改一处,设计当场逮住。 更头疼的是:同一套按钮,主按钮蓝色、危险按钮红色,你每个 Button 都贴一遍 .backgroundColor().fontColor().borderRadius().height(),代码臃肿到自己都不想看。

本文就讲清楚:ArkUI 里抽样式复用,该用 @Styles,要带参数定制就用 @Extend。这两个装饰器是鸿蒙 ArkUI 声明式 UI 的样式复用基石,搞懂它们你就能像攒乐高一样攒样式,设计改一处你改一处。

代码托管在 AtomGit,文末有链接,真机实拍截图作证。

适合人群:写过 ArkUI、被「同一按钮样式贴十遍」折磨过的同学。 不适合人群:还在学 @Builder 的同学------出门左转看我的上一篇。


一、先讲清楚:@Styles 和 @Extend 到底是啥

一句话:@Styles 抽一组样式属性,@Extend 扩展某个原生组件的样式(可带参)。

两个都解决「样式复用」,但能力不同:

维度 @Styles @Extend
能带参数吗 不能 能,任意多个参数
能限定组件类型吗 不能,通用任何组件 能,挂在指定组件上(如 @Extend(Text))
何时用 一组通用样式,多组件共用 给某类组件定制样式,按参微调

新手最容易混淆的决策:「这组样式要不要带参数?」 ------不要就用 @Styles,要就用 @Extend。就这么简单。


二、@Styles:一组通用样式,多组件共用

最小例子:

typescript 复制代码
@Styles
function cardStyle() {
  .backgroundColor('#fff')
  .borderRadius(10)
  .padding(14)
  .width('100%')
  .shadow({ radius: 6, color: '#1A000000', offsetX: 0, offsetY: 2 })
}

用的时候直接调:

typescript 复制代码
Column({ space: 6 }) {
  Text('鸿蒙开发实战').fontSize(16).fontWeight(FontWeight.Bold)
  Text('¥ 89').fontSize(14).fontColor('#FF4D4F')
}
.alignItems(HorizontalAlign.Start)
.cardStyle()    // ← 一行调样式

三张卡片都加 .cardStyle(),样式统一,设计改圆角你只改 cardStyle 里一处。

两种 @Styles 写法

写法 位置 何时用
全局 @Styles function Foo() 组件外 跨页面复用,放工具文件
组件内 @Styles Foo() struct 里 只本组件用

本 demo 用的是全局写法。

一个新手必踩的坑

@Styles 函数体里只能写样式属性方法 (以 . 开头那些),不能写组件构造。下面这样错:

typescript 复制代码
// ❌ 错:@Styles 里塞组件构造
@Styles function badStyle() {
  Text('something')    // 报错,@Styles 不接组件
  .backgroundColor('#fff')
}

// ✅ 对:@Styles 只接样式属性
@Styles function goodStyle() {
  .backgroundColor('#fff')
  .borderRadius(10)
}

@Styles 抽的是「样式」,不是「UI 块」------抽 UI 块那是 @Builder 的活(上一篇讲过)。


三、@Extend:扩展某个原生组件,可带参数

@Styles 解决了「通用样式」,但有个场景它搞不定:样式要按参数微调

比如标题文字:大标题字号 20、色 #222;小标题字号 14、色 #888------同一组件 Text,样式随参数变。这时候用 @Extend,它挂在指定组件上,能带任意参数:

typescript 复制代码
@Extend(Text)
function titleText(size: number, color: string) {
  .fontSize(size)
  .fontWeight(FontWeight.Bold)
  .fontColor(color)
  .maxLines(1)
  .textOverflow({ overflow: TextOverflow.Ellipsis })
}

调的时候传参:

typescript 复制代码
Text('这是大标题').titleText(20, '#222')
Text('这是小标题').titleText(14, '#888')
Text('这是超长标题会被省略号截断因为设置了 maxLines 和 textOverflow').titleText(14, '#555')

同一个 @Extend(Text),三种参数,三种字号/色------代码量从「每处四五行」缩到「每处一行带参」。

@Extend(Button) 主题色示例

按钮主题色是 @Extend 最实用的场景之一:

typescript 复制代码
@Extend(Button)
function primaryBtn() {
  .backgroundColor('#007DFF')
  .fontColor('#fff')
  .borderRadius(6)
  .height(40)
}

@Extend(Button)
function dangerBtn() {
  .backgroundColor('#FF4D4F')
  .fontColor('#fff')
  .borderRadius(6)
  .height(40)
}

用的时候:

typescript 复制代码
Row({ space: 10 }) {
  Button('确认').primaryBtn()
  Button('取消').dangerBtn()
}

一处定义主题色,N 处复用,以后改主色只改一处。


四、动手:一个 demo 同台演示三种复用姿势

demo 做了三件事:

  1. @Styles :三张卡片用同一组 cardStyle 样式
  2. @Extend(Text):同一扩展,不同字号/色参数
  3. @Extend(Button):主按钮/危险按钮两种主题色

4.1 @Styles 卡片样式

typescript 复制代码
@Styles
function cardStyle() {
  .backgroundColor('#fff')
  .borderRadius(10)
  .padding(14)
  .width('100%')
  .shadow({ radius: 6, color: '#1A000000', offsetX: 0, offsetY: 2 })
}

4.2 @Extend(Text) 标题扩展

typescript 复制代码
@Extend(Text)
function titleText(size: number, color: string) {
  .fontSize(size)
  .fontWeight(FontWeight.Bold)
  .fontColor(color)
  .maxLines(1)
  .textOverflow({ overflow: TextOverflow.Ellipsis })
}

4.3 @Extend(Button) 主题色扩展

typescript 复制代码
@Extend(Button)
function primaryBtn() {
  .backgroundColor('#007DFF')
  .fontColor('#fff')
  .borderRadius(6)
  .height(40)
}

@Extend(Button)
function dangerBtn() {
  .backgroundColor('#FF4D4F')
  .fontColor('#fff')
  .borderRadius(6)
  .height(40)
}

4.4 主页面:三种姿势同台展示

typescript 复制代码
@Entry
@Component
struct Index {
  build() {
    Column({ space: 12 }) {
      Text('@Extend / @Styles 样式复用 Demo').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 16 })

      // ① @Styles:三张卡片用同一组样式
      Column({ space: 10 }) {
        Column({ space: 6 }) {
          Text('鸿蒙开发实战').fontSize(16).fontWeight(FontWeight.Bold)
          Text('¥ 89').fontSize(14).fontColor('#FF4D4F')
        }.alignItems(HorizontalAlign.Start).cardStyle()

        Column({ space: 6 }) {
          Text('ArkUI 进阶指南').fontSize(16).fontWeight(FontWeight.Bold)
          Text('¥ 129').fontSize(14).fontColor('#FF4D4F')
        }.alignItems(HorizontalAlign.Start).cardStyle()

        Column({ space: 6 }) {
          Text('DevEco Studio 全解').fontSize(16).fontWeight(FontWeight.Bold)
          Text('¥ 79').fontSize(14).fontColor('#FF4D4F')
        }.alignItems(HorizontalAlign.Start).cardStyle()
      }.width('100%')

      // ② @Extend(Text):同一扩展,不同参数复用
      Column({ space: 8 }) {
        Text('这是大标题').titleText(20, '#222')
        Text('这是小标题').titleText(14, '#888')
        Text('这是超长标题会被省略号截断因为设置了 maxLines 和 textOverflow').titleText(14, '#555')
      }.width('100%').padding(14).backgroundColor('#fff').borderRadius(10)

      // ③ @Extend(Button):同一按钮组件,不同主题色
      Row({ space: 10 }) {
        Button('确认').primaryBtn()
        Button('取消').dangerBtn()
      }.width('100%').padding({ left: 14, right: 14 })
    }
    .padding(16).backgroundColor('#F5F6F8').height('100%').width('100%')
  }
}

五、真机实拍:三种复用姿势跑起来长这样

我把这个 demo 装到真机上跑(鸿蒙 6.1.1.125, API 24),下面这张是真机实拍,没有任何 P 图。

整体效果:三个区块依次展示「① @Styles 三张卡片共用样式」「② @Extend(Text) 同扩展不同参数」「③ @Extend(Button) 不同主题色」:

重点看画面:三张商品卡片样式完全一致(圆角/阴影/内边距统一,@Styles 复用成功);标题文字三种字号三种色共存(@Extend(Text) 带参复用);确认按钮蓝色 + 取消按钮红色(@Extend(Button) 主题色分流)。


六、@Styles vs @Extend vs @Builder:啥时候用哪个

新手最容易纠结的问题:既然都抽复用,三者咋取舍?

装饰器 抽什么 能带参数吗 何时用
@Builder UI 块(组件构造) 能(传数据) 抽一段 UI 结构复用
@Styles 样式属性(通用) 不能 抽一组通用样式多组件共用
@Extend 样式属性(限组件 + 带参) 给某类组件定制样式按参微调

一句话决策:抽 UI 结构用 @Builder,抽通用样式用 @Styles,抽带参定制样式用 @Extend


七、常见坑(都是血泪)

症状 解法
@Styles 里塞组件构造 编译报错「不能在 @Styles 里放组件」 @Styles 只接样式属性,要放组件改用 @Builder
@Styles 带参数 编译报错「@Styles 不支持参数」 带参样式改用 @Extend
@Extend(Text) 套在 Button 上调 编译报错或样式不生效 @Extend 限组件类型,挂 Text 只能 Text 调
@Extend 忘写组件类型 编译报错「@Extend 需指定组件」 @Extend(Button) function foo() 必须带组件名
@Extend 函数体里写非样式属性 编译报错 @Extend 函数体只接样式属性,逻辑写在组件里
三种装饰器混着写复用层叠 行为难预测 抽什么用什么,不要 @Builder 里再套 @Styles 嵌套

八、完整代码仓库

本文所有代码都已托管到 AtomGit,欢迎 clone、提 issue、点 star:

🔗 仓库地址 :atomgit.com/JaneConan/a...

仓库包含:

  • 完整的「样式三种复用姿势」demo 工程
  • Index.ets 主页面(@Styles 卡片 + @Extend(Text) 标题 + @Extend(Button) 主题色)
  • 可直接用 DevEco Studio 打开运行

九、下一步该学什么?

跑通这个 demo 之后,你的 ArkUI UI 复用就齐了三件套:@Builder + @Styles + @Extend。建议按这个顺序往下:

  1. Stage 模型状态管理 :AppStorageLocalStoragePersistentStorage------应用级、页面级、磁盘级三种状态容器
  2. @Watch 状态监听 :状态变了自动跑一段逻辑,比 onChange 更声明式
  3. @Observed/@ObjectLink 深层观察:嵌套对象的属性变化能触发重绘
  4. Provide/Consume 跨层传递:替代 props 层层透传,祖先直接给后代

写在最后

@Styles@Extend 的关系,本质是**「不带参的通用样式」和「带参的定制样式」的分离**。这思想在前端圈叫 mixin / styled-components,在鸿蒙圈叫 @Styles/@Extend,名字不同灵魂相通。

一旦你开始用样式复用思维写 UI,你会发现大部分页面样式都能拆成「一组通用 + 几个带参定制」。代码量少一半,改动只改一处,设计逮不住你漏改。

代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 跑起来,亲手改一个 @Extend 参数试试。

跑通了,回来评论区打个「1」,我看看有多少人真的动手了。🚀


作者:JaneConan 仓库:atomgit.com/JaneConan/a... 协议:Apache-2.0,随便用,别告我

相关推荐
orient17 小时前
为什么需要并发编程
后端
Cosolar17 小时前
AI Agent 架构原理详解:从一次提问到任务完成的完整闭环
人工智能·后端·架构
Conan在掘金17 小时前
鸿蒙 ArkUI 深水区:Stage 模型多维状态,从「组件内」跳到「应用级」的分水岭
后端
长栎18 小时前
用AI优化Redis缓存失效,我把热key问题想简单了
后端
面试鸭18 小时前
我说我们做 Agent 从来不怕死循环,面试官调出兜底日志:“第 47 次强制终止,是你半夜爬起来按停的?”
后端·面试·求职
小小猪的春天18 小时前
团队级 Code Review Skill 实践:4个人、4个AI、1套规则,CR从找bug变成做决策
后端·架构
长栎18 小时前
AI帮我写了个Spring Boot校验,线上漏掉了这组边界条件
后端
外滩运维专家18 小时前
Let's Encrypt 的 ACME 协议到底干了什么?用抓包带你看一遍
后端
程序员多吃鸭eatmoreduck18 小时前
# 开源初体验,39 天 800 Star:我把项目发到了哪里,流量到底从哪来
后端