HarmonyOS ArkTS:@Extend 装饰器详解——把“常用样式”变成组件的“专属能力”
做 ArkTS 声明式 UI 一段时间后,你大概率会遇到这个很真实的问题:
页面写着写着,
Text().fontSize().fontColor().margin()这种链式样式越来越长; 同一种风格的按钮、标题、卡片在 N 个页面都要写一遍; 复制粘贴多了,改一个细节要全局搜……很容易崩。
官方在 @Styles 的基础上,又提供了 @Extend: 它的思路不是“再抽一个样式对象”,而是更像——给某个组件“加一招” :
- 你可以把一串链式属性(字体/颜色/边距/背景/圆角/阴影…)
- 封装成一个扩展方法
- 以后用这个组件时,直接
.xxx()一下就套上统一风格
这就是 @Extend:定义扩展组件样式。 华为开发者官网
1. 先把 @Styles 和 @Extend 的差异讲清楚
很多人第一次看到 @Extend 会问: “这不就是 @Styles 的另一种写法吗?”
不完全是。
@Styles 更像“样式片段复用”
- 你写一段样式逻辑
- 在需要的地方引用(通常是作为样式集合/函数片段)
- 更偏“样式模块化”
@Extend 更像“给组件加方法”
- 它是绑定到某个组件类型上的
- 你写完以后,这个组件就像“长出了一个新能力”
- 使用体验更像:
Text("xxx").titleText()Row().pageContainer()Image($r("app.media.xx")).avatar()
✅ 如果你希望“写 UI 时更顺滑、少一坨链式样式”,@Extend 往往更爽。
2. @Extend 的核心语法(你只要记住这一个模板)
我建议你把它当成“固定模板”背下来:
@Extend(组件名)
function 扩展名() {
// 注意:这里通常直接写链式属性
this
.fontSize(20)
.fontColor(Color.Black)
.margin({ top: 8 })
}
关键点就三条:
@Extend(组件名):你要扩展谁?function 扩展名():扩展方法叫什么?this.xxx():在扩展体里写原来的链式样式
3. 最常用的 3 个实战例子(写完立刻能用)
3.1 给 Text 做“标题样式”:一招解决全局标题一致性
@Extend(Text)
function titleText() {
this
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#222')
.margin({ top: 12, bottom: 8 })
}
使用时就很舒服:
Column() {
Text('个人中心').titleText()
Text('这里是内容区域...')
}
你会发现:页面结构更干净,标题样式也不会散落到处都是。
3.2 给 Button 做“主按钮样式”:避免每页都写一遍
按钮是最值得统一的组件之一:圆角、背景色、宽高、字体、点击态……
@Extend(Button)
function primaryBtn() {
this
.backgroundColor('#0A59F7')
.borderRadius(12)
.height(44)
.width('100%')
.margin({ top: 12 })
}
用法:
Button('提交', () => {
// todo
}).primaryBtn()
你把“主按钮”的定义集中到一处后,后面想改风格(比如圆角从 12 改 10),全局秒生效。
3.3 给 Column/Row 做“页面容器样式”:统一页面内边距与背景
很多应用会有“页面容器的通用内边距 + 背景色 + 安全区处理”的需求:
@Extend(Column)
function pageContainer() {
this
.width('100%')
.height('100%')
.padding({ left: 16, right: 16, top: 12 })
.backgroundColor('#F6F7F9')
}
用法:
@Entry
@Component
struct DemoPage {
build() {
Column() {
Text('设置').titleText()
// ...内容
}.pageContainer()
}
}
这类容器扩展的价值非常高: 页面统一性 + 你写页面速度会快一倍。
4. @Extend 里能写什么?我的经验是分三类
A)纯样式类(最推荐)
- fontSize / color / padding / margin / background / border / radius / shadow
- 不带业务逻辑
- 用途:统一视觉规范
✅ 这是最稳、最不容易踩坑的写法。
B)轻量条件类(可用,但别贪)
有时你想要“同一个扩展,根据状态略微不同”,可以用参数(如果你的版本/规范允许这样写):
@Extend(Text)
function tagText(isHot: boolean) {
this
.fontSize(12)
.padding({ left: 8, right: 8, top: 2, bottom: 2 })
.borderRadius(10)
.backgroundColor(isHot ? '#FF4D4F' : '#EDEDED')
.fontColor(isHot ? '#FFF' : '#666')
}
用法:
Text('热').tagText(true)
Text('普通').tagText(false)
⚠️ 注意:参数化扩展很好用,但容易“无限膨胀”,建议只做轻量差异。
C)和交互强绑定的(谨慎)
比如你想在扩展里塞很多 onClick / 动画 / 状态处理。 这不是不行,但维护成本会上去。
我的建议是:
- 扩展里做“外观”
- 交互逻辑放回组件/页面里
这样团队协作时也更清晰。
5. 什么时候用 @Extend,什么时候用 @Styles?
我给你一个非常“工程化”的选择标准:
✅ 优先用 @Extend 的场景
- 你希望调用方式是
.xxx()(更像组件能力) - 你想让页面结构更干净
- 这一类样式明显属于某个组件(比如 Text 标题、Button 主按钮、Image 头像)
✅ 优先用 @Styles 的场景
- 你要做的是“通用样式组合”,不绑定具体组件
- 你要统一一套主题 token(颜色、字号、间距)
- 你希望在多个组件之间共享同一种布局/风格
官方也强调了:@Extend 是在 @Styles 基础上提供的能力,用于扩展组件样式。 华为开发者官网
6. 我自己落地时的组织方式(不花哨,但很抗打)
我一般会这样分文件(你抄这个结构就行):
entry/src/main/ets/
styles/
text.extend.ets
button.extend.ets
layout.extend.ets
image.extend.ets
然后每个文件里只做一类组件的扩展:
text.extend.ets:titleText、subTitleText、hintText、tagText…button.extend.ets:primaryBtn、dangerBtn、ghostBtn…layout.extend.ets:pageContainer、cardContainer、dividerLine…image.extend.ets:avatar、thumb、banner…
页面里只要 import 一下,你写 UI 会非常顺。
7. 最后给你一句“写得像人”的总结
@Extend 这个东西,我一开始也觉得是“锦上添花”。 但真正在项目里用起来后,感受是——它是把“重复劳动”一次性砍掉的那把刀。
你不再为了一个标题在每个页面抄四行样式; 你也不需要担心“这个按钮怎么和别的页面不一样”; 更重要的是:当产品说“整体风格要更圆润一点”,你不会心态爆炸。