鸿蒙 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 做了三件事:
@Styles:三张卡片用同一组cardStyle样式@Extend(Text):同一扩展,不同字号/色参数@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。建议按这个顺序往下:
- Stage 模型状态管理 :
AppStorage、LocalStorage、PersistentStorage------应用级、页面级、磁盘级三种状态容器 @Watch状态监听 :状态变了自动跑一段逻辑,比onChange更声明式@Observed/@ObjectLink深层观察:嵌套对象的属性变化能触发重绘Provide/Consume跨层传递:替代 props 层层透传,祖先直接给后代
写在最后
@Styles 和 @Extend 的关系,本质是**「不带参的通用样式」和「带参的定制样式」的分离**。这思想在前端圈叫 mixin / styled-components,在鸿蒙圈叫 @Styles/@Extend,名字不同灵魂相通。
一旦你开始用样式复用思维写 UI,你会发现大部分页面样式都能拆成「一组通用 + 几个带参定制」。代码量少一半,改动只改一处,设计逮不住你漏改。
代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 跑起来,亲手改一个 @Extend 参数试试。
跑通了,回来评论区打个「1」,我看看有多少人真的动手了。🚀
作者:JaneConan 仓库:atomgit.com/JaneConan/a... 协议:Apache-2.0,随便用,别告我