ArkTS 进阶之道(17):@Extend 专属属性扩展边界------为啥能收 fontSize 专属属性
本文是「ArkTS 进阶之道」系列第 17 篇,续「ArkUI 组件设计」阶段。上一篇讲 @Styles 绑组件通用属性复用样式集(篇 65)------@Styles 只支持通用属性不支持专属属性+不支持参数。本文讲专属属性扩展边界:@Extend 荬饰器绑特定组件专属属性+支持参数 ------根因在绑特定组件属性槽不是通用属性集,@Extend 荬饰器绑特定组件(Text/Button 等)专属属性+支持参数动态调,补 @Styles 不支持专属属性/参数的边界。能力系列篇 19 讲过 @Builder 怎么用,本文讲为哈 @Extend 收专属属性合法 @Styles 收专属属性报错------根因在特定组件属性槽绑定不是通用属性集。
一、开篇:@Extend 不是通用属性集,是绑特定组件专属属性的扩展荬饰器
你写 TypeScript/React 时,组件专属样式扩展是「魔法」(React 用 styled-components / CSS-in-JS 收组件专属属性):
typescript
// React 用 styled-components 收组件专属属性
const MyText = styled.span<{ size: number; color: string; bold: boolean }>`
font-size: ${props => props.size}px; ← 组件专属属性 fontSize
color: ${props => props.color}; ← 组件专属属性 color
font-weight: ${props => props.bold ? 'bold' : 'normal'};
`
function Component() {
return <MyText size={16} color="#dc2626" bold={true}>样式扩展</MyText>
}
// React 用 styled-components 收组件专属属性+参数,是返值收值魔法
你写鸿蒙 ArkTS 时,@Extend 绑特定组件专属属性扩展------不用返样式对象:
typescript
// ArkTS @Extend 绑特定组件(Text)专属属性+支持参数
@Extend(Text)
function MyTextStyle(size: number, color: ResourceColor, isBold: boolean = false) {
.fontSize(size) // ✅ Text 专属属性,@Extend 能写
.fontColor(color) // ✅ Text 专属属性,@Extend 能写
.fontWeight(isBold ? FontWeight.Bold : FontWeight.Normal)
.backgroundColor('#f0f0f0') // ✅ 通用属性也能写
.padding(6)
.borderRadius(4)
}
@Entry
@Component
struct Index {
build() {
Column() {
Text('样式扩展1')
.MyTextStyle(16, '#dc2626', true) // ✅ 链式调 @Extend 带参数绑 Text 专属属性
Text('样式扩展2')
.MyTextStyle(14, '#2563eb', false) // ✅ 多调用点复用同 @Extend 不同参数
}
}
}
// @Extend 绑特定组件(Text)专属属性+支持参数,不是通用属性集(不收 style 值)
魔法 vs 荬饰器的区别:React 把组件专属样式扩展当 styled-components 收值(返值收值魔法),ArkTS 把 @Extend 当「绑特定组件专属属性荬饰器」(不收值,绑特定组件属性槽+支持参数)。根因不是魔法是特定组件属性槽绑定------@Extend 链式调用不返样式对象,是绑特定组件专属属性的扩展荬饰器。
二、根因:@Extend 的特定组件属性槽绑定扩展机制
鸿蒙 ArkUI 的 @Extend 是特定组件属性槽绑定扩展------编译期把 @Extend 方法绑成特定组件(Text/Button 等)的专属属性槽,链式调用嵌属性槽复用专属属性,支持参数动态调,不是通用属性集,来自三重绑定机制。
机制 1:@Extend 编译期绑特定组件专属属性槽------不是通用属性集
@Extend 荬饰器编译期绑特定组件专属属性槽------把 @Extend 方法编成特定组件的可复用专属属性槽,不是通用属性集:
typescript
@Extend(Text) // ✅ 指定绑特定组件 Text
function MyTextStyle(size: number, color: ResourceColor, isBold: boolean = false) {
.fontSize(size) // ✅ Text 专属属性 fontSize(@Extend 能写)
.fontColor(color) // ✅ Text 专属属性 fontColor(@Extend 能写)
.fontWeight(isBold ? FontWeight.Bold : FontWeight.Normal)
.backgroundColor('#f0f0f0') // ✅ 通用属性也能写
.padding(6)
.borderRadius(4)
}
@Entry
@Component
struct Index {
build() {
Column() {
Text('样式扩展')
.MyTextStyle(16, '#dc2626', true) // 链式调嵌专属属性槽(绑 Text 属性槽)
}
}
}
// 编译期:@Extend(Text) MyTextStyle 绑成 Text 可复用专属属性槽
// 链式调用:.MyTextStyle() 嵌专属属性槽绑 Text(fontSize/fontColor/fontWeight)
编译期绑特定组件属性槽 :@Extend MyTextStyle 荬饰器编译期把方法绑成 Text 的可复用专属属性槽------@Extend(Text) 指定绑 Text 组件,方法里能写 Text 专属属性(fontSize/fontColor/fontWeight)。链式调用 .MyTextStyle(16, '#dc2626', true) 在 Text 上嵌专属属性槽。根因不是通用属性集是特定组件属性槽绑定。
机制 2:支持参数动态调专属属性------按参数变 fontSize/fontColor
@Extend 荬饰器 支持参数动态调专属属性------按参数变 fontSize/fontColor 等专属属性,补 @Styles 不支持参数的边界:
typescript
@Extend(Text)
function MyDynamicTextStyle(size: number, color: ResourceColor) {
.fontSize(size) // ✅ 按参数变字号(动态调)
.fontColor(color) // ✅ 按参数变字色(动态调)
.margin({ top: 4, bottom: 4 })
}
@Entry
@Component
struct Index {
@State count: number = 0
build() {
Column() {
Text(`动态字号=${10 + this.count} 红色`)
.MyDynamicTextStyle(10 + this.count, '#dc2626') // ✅ 按参数变字号(动态调)
Text(`动态字号=${12 + this.count} 荝色`)
.MyDynamicTextStyle(12 + this.count, '#2563eb') // ✅ 多调用点不同参数
Button('改 count')
.onClick(() => { this.count++ }) // count 变触发按参数重算字号
}
}
}
// @Extend 支持参数:按参数变 fontSize/fontColor 等专属属性(动态调)
// count 变触发按参数重算字号(10 + count / 12 + count),字号动态变
支持参数动态调 :@Extend 支持参数------按参数变 fontSize/fontColor 等专属属性,补 @Styles 不支持参数的边界。MyDynamicTextStyle(10 + this.count, '#dc2626') 按参数变字号(10+count),count 变触发按参数重算字号动态变。根因不是固定属性集是参数动态调专属属性槽绑定。
机制 3:@Extend 必须全局定义------不能写在组件内
@Extend 荬饰器 必须全局定义------加 function 关键字定义在组件外,不能写在组件内(跟 @Styles 可组件内/全局不同):
typescript
// ✅ @Extend 必须全局定义(加 function 关键字,写在组件外)
@Extend(Text)
function MyTextStyle(size: number, color: ResourceColor, isBold: boolean = false) {
.fontSize(size)
.fontColor(color)
.fontWeight(isBold ? FontWeight.Bold : FontWeight.Normal)
}
@Entry
@Component
struct Index {
// ❌ @Extend 不能写在组件内(必须全局)
// @Extend(Text)
// MyBadStyle() {
// .fontSize(16) ← @Extend 写组件内报错
// }
build() {
Column() {
Text('样式扩展')
.MyTextStyle(16, '#dc2626', true) // 调全局 @Extend
}
}
}
// @Extend 必须全局定义根因:绑特定组件属性槽是全局复用槽,不是组件内复用
// @Styles 可组件内/全局,@Extend 只能全局(绑特定组件全局复用槽)
必须全局定义约束:@Extend 必须全局定义------加 function 关键字定义在组件外,不能写在组件内。根因是 @Extend 绑特定组件属性槽是全局复用槽(所有组件都能调),不是组件内复用槽。@Styles 可组件内/全局(组件内能访问 this/@State),@Extend 只能全局(不能访问 this/@State,但能收参数动态调)。
机制 4:@Extend vs @Styles 边界------专属属性槽 vs 通用属性集
@Extend 绑特定组件专属属性槽+支持参数 ,@Styles 绑通用属性集+不支持参数+不支持专属属性------根因都是属性绑定但绑的属性槽类型不同:
typescript
// ✅ @Extend:绑特定组件(Text)专属属性槽+支持参数
@Extend(Text)
function MyTextStyle(size: number, color: ResourceColor, isBold: boolean = false) {
.fontSize(size) // ✅ Text 专属属性,@Extend 能写
.fontColor(color) // ✅ Text 专属属性,@Extend 能写
.fontWeight(isBold ? FontWeight.Bold : FontWeight.Normal)
.backgroundColor('#f0f0f0') // ✅ 通用属性也能写
.padding(6)
}
// ✅ @Styles:绑通用属性集+不支持参数+不支持专属属性
@Styles
function MyCommonBox() {
.width('88%') // ✅ 通用属性,@Styles 能写
.backgroundColor('#e0e0e0') // ✅ 通用属性,@Styles 能写
.padding(8)
.borderRadius(6)
// ❌ .fontSize(16) ← Text 专属属性,@Styles 报错(@Extend 才能写)
}
@Entry
@Component
struct Index {
build() {
Column() {
Text('专属属性扩展')
.MyTextStyle(16, '#dc2626', true) // ✅ @Extend 绑 Text 专属属性+参数
Column() { Text('通用属性集') }
.MyCommonBox() // ✅ @Styles 绑通用属性集
}
}
}
// @Extend 绑特定组件专属属性槽+支持参数(全局定义,不能访问 this)
// @Styles 绑通用属性集+不支持参数(可组件内/全局,组件内能访问 this)
@Extend vs @Styles 边界:@Extend 绑特定组件专属属性槽(fontSize/fontColor/fontWeight 等专属属性)+支持参数+必须全局定义。@Styles 绑通用属性集(width/backgroundColor/padding 等通用属性)+不支持参数+可组件内/全局。根因都是属性绑定但绑的属性槽类型不同------@Extend 绑特定组件专属属性槽(Text/Button 等专属),@Styles 绑通用属性集(所有组件共有)。
三、真机配图:@Extend 专属属性扩展边界------绑 Text 专属属性+支持参数
初始态(@Extend 调用1 显 fontSize=16 红色加粗、调用2 显 fontSize=14 荝色正常、动态调按 count=0 显字号 10/12 均专属属性扩展边界初始值):

点调按钮后(动态字号按 count=1 变了、@Extend 带参数调 Text 专属属性均专属属性扩展边界对比证据齐):

对比证据:点改 count 按钮后动态字号按 count=1 变了(10+1=11、12+1=13,@Extend 带参数动态调字号),调用1 显 fontSize=16 红色加粗、调用2 显 fontSize=14 荝色正常(@Extend 绑 Text 专属属性 fontSize/fontColor/fontWeight)。@Extend 不是通用属性集是绑特定组件专属属性的扩展荬饰器------@Extend 荬饰器绑特定组件(Text)专属属性槽+支持参数,补 @Styles 不支持专属属性/参数的边界。
四、真解法:@Extend 专属属性扩展的三个场景
场景 1:@Extend 绑 Text 专属属性+参数(90% 场景首选,文本样式扩展)
typescript
@Extend(Text)
function MyTextStyle(size: number, color: ResourceColor, isBold: boolean = false) {
.fontSize(size) // ✅ 按参数变字号
.fontColor(color) // ✅ 按参数变字色
.fontWeight(isBold ? FontWeight.Bold : FontWeight.Normal)
.backgroundColor('#f0f0f0')
.padding(6)
.borderRadius(4)
}
@Entry
@Component
struct Index {
build() {
Column() {
Text('样式扩展1')
.MyTextStyle(16, '#dc2626', true) // 调用点1 嵌专属属性槽1(字号16/红/加粗)
Text('样式扩展2')
.MyTextStyle(14, '#2563eb', false) // 调用点2 嵌专属属性槽2(字号14/蓝/正常)
}
}
}
为哈能跑 :@Extend 绑 Text 专属属性+参数------多个调用点嵌专属属性槽,参数不同显不同文本样式。首选这个,90% 的场景文本样式扩展用 @Extend 绑 Text 专属属性+参数就够。要写「文本样式扩展(fontSize/fontColor/fontWeight 按参数变)」时用这个------不用 @Styles(不支持专属属性)或 inline 样式,@Extend 荬饰器绑 Text 专属属性槽链式调用带参数嵌槽。
场景 2:@Extend 绑 Button 专属属性(按钮样式扩展)
typescript
@Extend(Button)
function MyButtonStyle(buttonType: CapsuleStyleType = CapsuleStyleType.CAPSULE) {
.buttonStyle(buttonType) // ✅ Button 专属属性 buttonStyle
.buttonStyle(CapsuleStyleType.CAPSULE) // ✅ Button 专属属性
.width('80%')
.height(44)
.backgroundColor('#2563eb') // ✅ 通用属性也能写
.borderRadius(22)
}
@Entry
@Component
struct Index {
build() {
Column() {
Button('胶囊按钮')
.MyButtonStyle(CapsuleStyleType.CAPSULE) // 调 @Extend 绑 Button 专属属性
}
}
}
为哈能跑:@Extend 绑 Button 专属属性------buttonStyle 等 Button 专属属性塞 @Extend,按参数变按钮样式。要写「按钮样式扩展(buttonStyle 等专属属性按参数变)」时用这个------@Extend 绑 Button 专属属性槽,不用 @Styles(不支持 Button 专属属性 buttonStyle)。
场景 3:@Extend 带状态动态调(按 @State 参数变专属属性)
typescript
@Extend(Text)
function MyDynamicTextStyle(size: number, color: ResourceColor) {
.fontSize(size) // ✅ 按参数变字号
.fontColor(color) // ✅ 按参数变字色
.margin({ top: 4, bottom: 4 })
}
@Entry
@Component
struct Index {
@State count: number = 0
build() {
Column() {
Text(`动态字号=${10 + this.count} 红色`)
.MyDynamicTextStyle(10 + this.count, '#dc2626') // ✅ 按参数变字号(动态调)
Button('改 count')
.onClick(() => { this.count++ }) // count 变触发按参数重算字号
}
}
}
// @Extend 带状态动态调:按 @State count 参数变专属属性(字号动态变)
// 注意:@Extend 全局定义不能访问 this,但能收参数(调用时传 this.count 进去)
为哈能跑 :@Extend 带状态动态调------调用时传 this.count 进参数,@State 变触发按参数重算专属属性。要写「按状态变专属属性(字号/字色等动态变)」时用这个------@Extend 收参数(调用时传 this.count),@State 变触发按参数重算专属属性重渲。注意:@Extend 全局定义不能访问 this(不像组件内 @Styles 能访问 this),但能收参数绕坑。
五、一句话哲学
@Extend 不是通用属性集,是绑特定组件专属属性的扩展荬饰器。 ArkUI 的 @Extend 荬饰器编译期绑特定组件(Text/Button 等)专属属性槽,链式调用嵌属性槽复用专属属性+支持参数动态调。根因不是通用属性集是特定组件属性槽绑定------@Extend 编译期绑特定组件专属属性槽(fontSize/fontColor 等)+ 支持参数动态调(按参数变专属属性)+ 必须全局定义(绑特定组件全局复用槽)+ @Extend vs @Styles 边界(专属属性槽 vs 通用属性集)。对比 React styled-components 收组件专属属性值,ArkTS @Extend 绑特定组件专属属性槽不收值。
组件设计阶段串讲 :@Builder 绑渲染树节点复用(篇 63,调用点嵌节点实例复用 UI 不返值)→ @BuilderParam 绑渲染树节点参数槽收片段(篇 64,父组件传片段嵌槽复用子组件结构)→ @Styles 绑组件通用属性复用样式集(篇 65,链式调用嵌属性集复用样式不返值)→ @Extend 绑特定组件专属属性槽扩展+支持参数(篇 66,链式调用嵌专属属性槽复用专属属性不返值)------四篇讲清 ArkUI 组件复用哲学:渲染树/属性绑定复用(@Builder 绑节点 / @BuilderParam 绑参数槽 / @Styles 绑通用属性 / @Extend 绑专属属性槽),根因都是绑渲染树/属性不收值不返值。从「UI 片段复用」(@Builder/@BuilderParam)到「样式集复用」(@Styles)到「专属属性扩展」(@Extend)扩展。
@Styles vs @Extend 对比总结:@Styles 绑通用属性集(所有组件共有 width/backgroundColor 等)+ 不支持参数 + 可组件内/全局(组件内能访问 this)。@Extend 绑特定组件专属属性槽(Text 的 fontSize/fontColor 等)+ 支持参数动态调 + 必须全局(不能访问 this 但能收参数)。要写通用样式集复用用 @Styles,要写专属属性扩展+参数动态调用 @Extend。
系列预告:下篇(篇 67,如续)讲 AttributeModifier 动态样式边界(跨文件导出+多态样式+业务逻辑),或系列就此打住。五阶段哲学体系:类型哲学(50-52)→ 作用域哲学(53-55)→ 状态哲学(56-59)→ 渎染哲学(60-62)→ 组件设计(63-66)讲清 ArkTS/ArkUI 进阶哲学。
能力系列回链
| 能力系列篇 | 本文进阶点 |
|---|---|
| 篇 19 @Builder 用法 | @Extend 专属属性扩展边界根因(绑特定组件专属属性槽) |
| 篇 16 组件复用用法 | 上一篇:@Styles 绑通用属性集根因 |
| 篇 13 @State 基础用法 | 状态哲学:@State 贬值就刷 UI 依赖追踪 |
真机 demo 完整代码
typescript
// 篇 66 demo:@Extend 专属属性扩展 vs @Styles 只收通用属性对比
// 对比:@Extend 绑特定组件专属属性+支持参数合法 vs @Styles 不支持专属属性
// ✅ @Extend 荬饰器:绑特定组件(Text)专属属性,支持参数
@Extend(Text)
function MyTextStyle(size: number, color: ResourceColor, isBold: boolean = false) {
.fontSize(size) // ✅ Text 专属属性,@Extend 能写
.fontColor(color) // ✅ Text 专属属性,@Extend 能写
.fontWeight(isBold ? FontWeight.Bold : FontWeight.Normal) // ✅ Text 专属属性
.backgroundColor('#f0f0f0') // ✅ 通用属性也能写
.padding(6)
.borderRadius(4)
}
// ✅ @Extend 带参数动态调样式(按参数变 fontSize/fontColor)
@Extend(Text)
function MyDynamicTextStyle(size: number, color: ResourceColor) {
.fontSize(size) // ✅ 按参数变字号
.fontColor(color) // ✅ 按参数变字色
.margin({ top: 4, bottom: 4 })
}
@Entry
@Component
struct Index {
@State count: number = 0
@State log: string = '(未操作)'
// ✅ @Styles 荬饰器:只写通用属性(对比证据)
@Styles
MyCommonBox() {
.width('88%')
.backgroundColor('#e0e0e0')
.padding(8)
.borderRadius(6)
}
build() {
Column({ space: 12 }) {
Text('篇 66 配图:@Extend 专属属性扩展边界')
.fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 })
Text('@Extend 绑 Text 专属属性+支持参数 vs @Styles 只收通用属性(对比证据)')
.fontSize(12).fontColor('#888').margin({ bottom: 16 })
Column({ space: 6 }) {
Text(`count = ${this.count}`).fontSize(15).fontWeight(FontWeight.Bold)
Text(`日志:${this.log}`).fontSize(12).fontColor('#333').margin({ top: 4 })
}
.width('92%').padding(12).backgroundColor('#f5f5f5').borderRadius(8)
// ✅ @Extend 调用:绑 Text 专属属性+支持参数
Text('@Extend 调用1:')
.fontSize(13)
.fontColor('#888')
Text('样式扩展1:fontSize=16 红色加粗')
.MyTextStyle(16, '#dc2626', true) // ✅ @Extend 带参数调 Text 专属属性
Text('样式扩展2:fontSize=14 荝色正常')
.MyTextStyle(14, '#2563eb', false) // ✅ 多调用点复用同 @Extend 不同参数
// ✅ @Extend 带参数动态调样式
Text('@Extend 带参数动态调:')
.fontSize(13)
.fontColor('#888')
.margin({ top: 8 })
Text(`动态字号=${10 + this.count} 红色`)
.MyDynamicTextStyle(10 + this.count, '#dc2626') // ✅ 按参数变字号(动态调)
Text(`动态字号=${12 + this.count} 荝色`)
.MyDynamicTextStyle(12 + this.count, '#2563eb') // ✅ 多调用点不同参数
// ✅ @Styles 只收通用属性(对比证据)
Text('@Styles 只收通用属性:')
.fontSize(13)
.fontColor('#888')
.margin({ top: 8 })
Column() { Text('通用属性集复用') }
.MyCommonBox() // ✅ @Styles 只绑通用属性,不支持专属属性
// ❌ @Styles 不支持专属属性(对比证据,注释掉避编译炸)
// @Styles
// MyBadTextStyle() {
// .fontSize(16) ← fontSize 是 Text 专属属性,@Styles 报错(@Extend 才能写)
// }
Button('改 count(@Extend 带参数动态调样式也刷)')
.width('92%').height(44).fontSize(14)
.onClick(() => {
this.count++ // ✅ count 变 @Extend 带参数动态调样式也刷
this.log = `count=${this.count}(@Extend 带参数动态调字号也刷)`
})
}
.width('100%').height('100%').alignItems(HorizontalAlign.Center)
}
}
写鸿蒙 ArkUI 记住 :@Extend 不是通用属性集是绑特定组件专属属性的扩展荬饰器------@Extend 荬饰器编译期绑特定组件(Text/Button 等)专属属性槽,链式调用嵌属性槽复用专属属性+支持参数动态调。根因不是通用属性集是特定组件属性槽绑定------@Extend 编译期绑特定组件专属属性槽(fontSize/fontColor 等)+ 支持参数动态调(按参数变专属属性)+ 必须全局定义(绑特定组件全局复用槽)+ @Extend vs @Styles 边界(专属属性槽 vs 通用属性集)。@Extend 绑 Text 专属属性+参数用文本样式扩展(首选,90% 场景),@Extend 绑 Button 专属属性用按钮样式扩展,@Extend 带状态动态调用按 @State 参数变专属属性(调用时传 this.count)。绑特定组件专属属性槽+支持参数不收值是 ArkUI 组件设计哲学核心!