鸿蒙(HarmonyOS)-- 装饰器(UI装饰器)_鸿蒙装饰器
鸿蒙装饰器是 ArkTS 语言中一项基于 TypeScript 装饰器特性深度扩展的核心能力。它在兼容 TypeScript 原有装饰器语法的基础上,针对鸿蒙生态进行了功能强化和场景优化,使得开发者能够通过声明式语法为数据、组件等元素注入\"增强特性\"(如状态管理、生命周期控制、UI渲染优化等)。这种能力类似于为代码赋予\"超能力\",极大提升了开发效率和组件复用性。
目录
1、@Styles装饰器:定义组件重用样式
2、@Extend装饰器:定义扩展组件样式
4、@BuilderParam装饰器:引用@Builder函数
5、@LocalParam装饰器:维持组件父子关系
6、@Require装饰器:校验构造传参
7、@Reusable装饰器:组件复用
7.1、不使用@Reusable装饰
7.2、使用@Resuable装饰
8、@AnimatableExtend装饰器:定义可动画属性
1、@Styles装饰器:定义组件重用样式
@Styles
装饰器:可以将多条样式设置成一个方法,直接在组件声明位置调用。通过@Styles
装饰器可以快速定义并复用自定义样式。@Styles
可以定义在组件内或全局- 全局定义时,需在方法名前面添加
function
关键字。 - 组件内定义时,则不需要添加
function
关键字。
- 全局定义时,需在方法名前面添加
注意:
@Styles
装饰器只支持通用属性和通用事件。@Styles
装饰器不支持导出使用,不支持export
@Styles
装饰器不支持使用参数
使用方法
@Entry@Componentstruct Index { @State message: string = \'Hello World\' build() { Column({ space: 10 }) { Text(this.message) .MyStyles() // 使用 } .height(\'100%\') .width(\'100%\') } // 组件内定义【 不需要 function 关键字 】 @Styles MyStyles() { .width(100) .height(100) .backgroundColor(Color.Pink) }}// 优先使用组件内的@Styles// 全局定义【 需要 function 关键字 】@Stylesfunction MyStyles() { .width(100) .height(100) .backgroundColor(Color.Red)}
2、@Extend装饰器:定义扩展组件样式
@Extend
装饰器可以将多条样式设置成一个方法,直接在组件声明位置调用。通过@Extend
装饰器可以快速定义并复用自定义样式。@Extend
只能支持在全局定义- 在使用
@Extend
装饰器时,需要指定一个你将要扩展的组件
注意:
@Extend
装饰器只支持指定组件所能使用的属性和事件
如:你指定的组件是Text
,那么只能添加Text
可以使用的属性和事件
如:你指定的组件是Button
,那么只能添加Button
可以使用的属性和事件@Extend
装饰器不支持导出使用,不支持export
@Extend
装饰器支持使用参数
基础使用
@Entry@Componentstruct Index { @State message: string = \'Hello World\' build() { Column({ space: 10 }) { Text(this.message) .MyExtend() // 使用 } .height(\'100%\') .width(\'100%\') }}// 全局定义@Extend(Text)function MyExtend() { .width(100) .height(100) .backgroundColor(Color.Pink) .fontSize(30)// Text 组件支持的属性 .fontColor(Color.Red) // Text 组件支持的属性}
传参使用
参数可以为状态变量或者函数类型
- 状态变量用于样式书写
- 函数类型一般作为事件的回调函数使用
@Entry@Componentstruct Index { @State message: string = \'Hello World\' build() { Column({ space: 10 }) { Text(this.message) .MyExtend(Color.Red, () => console.log(\'你好 世界\')) // 使用( 传递 ) } .height(\'100%\') .width(\'100%\') }}// 全局定义@Extend(Text)function MyExtend(color: Color, clickHandler: () => void) { .width(100) .height(100) .backgroundColor(Color.Pink) .fontSize(30) .fontColor(color) // 使用 color 参数 .onClick(clickHandler) // 使用 clickHandler 参数}
3、@Builder 装饰器:自定义构建函数
@Builder
装饰器:可以将一段重复使用的UI元素抽象成一个方法,在build方法里调用。其内部UI结构固定,仅与使用方进行数据传递。
@Builder
装饰器有两种使用方式,分别是:- 在自定义组件内部的私有自定义构建函数。
- 定义在全局的全局自定义构建函数。
注意:
@Builder
装饰器定义的自定义构建函数在使用的时候时支持使用参数的,但是在自定义构建函数内部,不允许修改参数的值- 参数只能是一个如果是两个以上,不会引起
@Builder
内UI结构刷新@Builder
在@ComponentV2
组件中,需要配合@ObservedV2
和@Trace
装饰器使用才会触发@Builder内UI刷新
自定义构建函数
@Entry@Componentstruct Index { // 私有自定义构建函数 @Builder MyBuilder() { Row({ space: 10 }) { Text(\'文本1\') Text(\'文本2\') } } build() { Column({ space: 10 }) { Text(\'hello world\') // 使用私有自定义构建函数 this.MyBuilder() // 使用全局自定义构建函数 MyGlobalBuilder() } .height(\'100%\') .width(\'100%\') }}// 全局自定义构建函数@Builderfunction MyGlobalBuilder() { Row({ space: 10 }) { Text(\'文本1\') Text(\'文本2\') }}
支持使用参数
@Entry@Componentstruct Index { @State msg: string = \'文本1\' build() { Column({ space: 10 }) { Text(\'hello world\') // 【 按值传递 】 // 当 Index 内的 msg 发生变化的时候, MyBuilder1 内的 UI 不会刷新 MyBuilder1(this.msg) // 【 按引用传递 】 // Index 内的 msg 发生变化的时候, MyBuilder2 内的 UI 会刷新 //【 保证 MyBuilder 只有一个参数 】 //【 这里必须字面量形式书写引用类型 】 MyBuilder2({ text: this.msg }) } .height(\'100%\') .width(\'100%\') }}@Builderfunction MyBuilder1(message: string) { Row({ space: 10 }) { Text(message) Text(\'文本1\') }}class Message { this.text = \"\"}@Builderfunction MyBuilder2(messageObj: Message) { Row({ space: 10 }) { Text(messageObject.text) Text(\'文本2\') }}
传递两个以上参数
@Entry@Componentstruct Index { @State msg: string = \'文本1\' build() { Column({ space: 10 }) { Text(\'hello world\') // 【 传递两个以上参数 】 // 当 Index 内的 msg 发生变化的时候, MyBuilder1 内的 UI 不会刷新 MyBuilder1({ text: this.msg }, this.msg) } .height(\'100%\') .width(\'100%\') }}@Builderfunction MyBuilder1(messageObj: Message, message: string) { Row({ space: 10 }) { Text(message) Text(messageObj.text) }}class Message { this.text = \"\"}
@Builder
在@ComponetV2
组件中使用
@Builder
在@ComponentV2
组件中,需要配合@ObservedV2
和@Trace
装饰器使用才会触发@Builder内UI刷新
@ObservedV2class Params { @Trace count: number = 0}@Builderfunction MyBuilder(params: Params) { Column({ space: 10 }) { Text(`MyBuilder 内 count => ${ params.count }`) } .width(200) .backgroundColor(Color.Pink)}@Entry@ComponentV2struct Index { @Local obj: Params = new Params() build() { Column({ space: 10 }) { Text(\'hello world\') Text(`Index 组件内 count => ${ this.obj.count }`) // 这两种传参方式都能触发UI刷新 MyBuilder({ count: this.obj.count }) MyBuilder(this.obj) Button(\'change\') .onClick(() => { this.obj.count++ }) } .height(\'100%\') .width(\'100%\') }}
4、@BuilderParam装饰器:引用@Builder函数
@BuilderParam
装饰器是承接“自定义构建函数”的。一般来说,出现父子组件时,是可以把“自定义构建函数”当作参数传递给子组件的。
那么我们在传递的过程中,就需要定义状态变量来接受,此时就需要使用
@BuilderParam
一般用法
调用子组件,把自定义构建函数当作参数来传递
@Entry@Componentstruct Index { @State message: string = \'文本111\' build() { Column() { // 传递自定义构建函数 ChildComponent({customBuilder: MyBuilder}) } .height(\'100%\') .width(\'100%\') }}// 定义自定义构建函数@Builderfunction MyBuilder() { Row() { Text(\'我是父组的自定义构建函数MyBuilder\') }}// 自定义子组件@Componentstruct ChildComponent { // 定义一个空Builder,解决父组件不传自定义构建函数时报错的问题,把空的Builder作为默认值 @Builder EmptyBuilder () {} // 使用@BuilderParam接收父组件传递过来的Builder @BuilderParam customBuilder: () => void = this.EmptyBuilder build() { Column() { Text(\'我是自定义子组件\') // 在子组件中使用 this.customBuilder() } }}
尾随闭包用法
我们发现上面写法中,父组件调用子组件时,传参写法略显麻烦。
此时也可以使用尾随闭包的形式进行自定义构建函数的传递
【其实就是在调用子组件时在{}内书写结构】
【注意:子组件内有且只有一个
@BuilderParam
时才可以,否则报错】
@Entry@Componentstruct Index { @State message: string = \'文本111\' build() { Column() { // 尾随闭包写法,要求子组件内有且子有一个@BuilderParam ChildComponent() { Text(\'我是尾随闭包写法\') MyBuilder() } } .height(\'100%\') .width(\'100%\') }}// 定义自定义构建函数@Builderfunction MyBuilder() { Row() { Text(\'我是父组的自定义构建函数MyBuilder\') }}// 自定义子组件@Componentstruct ChildComponent { // 定义一个空Builder,解决父组件不传自定义构建函数时报错的问题,把空的Builder作为默认值 @Builder EmptyBuilder() {} // 使用@BuilderParam接收父组件传递过来的Builder @BuilderParam customBuilder: () => void = this.EmptyBuilder build() { Column() { Text(\'我是自定义子组件\') // 在子组件中使用 this.customBuilder() } }}
@BuilderParam中的this指向
因为自定义构建函数也是函数,内部可以书写一些事件,那么就涉及到this指向问题。
传参写法: this指向子组件
尾随闭包写法: this指向父组件
@Entry@Componentstruct Index { @State message: string = \'parent\' // 定义自定义构建函数 @Builder MyBuilder() { Row() { Text(\'我是父组的自定义构建函数MyBuilder\') Text(`this=>${this.message}`) }} build() { Column() { // 尾随闭包写法,this指向父组件 ChildComponent() { Text(\'我是尾随闭包写法\') this.MyBuilder() } // 传参写法,this指向子组件 ChildComponent({customBuilder: this.MyBuilder}) } .height(\'100%\') .width(\'100%\') }}// 自定义子组件@Componentstruct ChildComponent { @State message: string = \'Child\' // 定义一个空Builder,解决父组件不传自定义构建函数时报错的问题,把空的Builder作为默认值 @Builder EmptyBuilder() { } // 使用@BuilderParam接收父组件传递过来的Builder @BuilderParam customBuilder: () => void = this.EmptyBuilder build() { Column() { Text(\'我是自定义子组件\') this.customBuilder() } }}
5、@LocalParam装饰器:维持组件父子关系
@LocalBuilder
装饰器从使用者的角度上来说和@Builder
基本一样。【注意:
@LocalBuilder
装饰器只能定义私有的,不能定义全局的。】一般来说,如果只有一个组件,我们不需要使用
@LocalBuilder
只有当两个及以上组件出现,并且要把自定义构建函数定义在父组件内然后传递到子组件使用的时候,我们才有可能会用到@LocalBuilder
。【之所以是可能用到,是因为如果我们不关心this指向,也不需要用到】
@LocalBuilder
和@Builder
的区别:
- 定义位置
@Builder
:可以定义在组件内(私有)和全局@LocalBuilder
:只能定义在组件内,不能定义在全局
- this指向
@Builder
:this指向是自定义构建函数“用在哪就指向哪”@LocalBuilder
:this指向是自定义构建函数“写在哪就是哪”
@Entry@Componentstruct Index { @State message: string = \'parent\' // @LocalBuilder只能定义在组件内部 @LocalBuilder MyLocalBuilder() { Row() { Text(\'我是父组的自定义构建函数MyLocalBuilder\') Text(`this=>${this.message}`) }} build() { Column() { // 尾随闭包写法,传递给子组件,this指向父组件 ChildComponent() { Text(\'我是尾随闭包写法\') this.MyLocalBuilder() } // 传参写法,传递给子组件,this指向父组件 ChildComponent({customBuilder: this.MyLocalBuilder}) // 组件内直接调用,this指向父组件 this.MyLocalBuilder() } .height(\'100%\') .width(\'100%\') }}// 自定义子组件@Componentstruct ChildComponent { @State message: string = \'Child\' // 定义一个空Builder,解决父组件不传自定义构建函数时报错的问题,把空的Builder作为默认值 @Builder EmptyBuilder() { } // 使用@BuilderParam接收父组件传递过来的Builder @BuilderParam customBuilder: () => void = this.EmptyBuilder build() { Column() { Text(\'我是自定义子组件\') this.customBuilder() } }}
6、@Require装饰器:校验构造传参
@Require
装饰器是校验@Prop、@State、@Provide、@BuilderParam
和普通变量(无状态装饰器装饰的变量)是否需要构造传参的装饰器。其实就是在子组件定义一些内容(状态变量和无状态变量)的时候,父组件调用该子组件是否需要传参。
- 没有
@Require
装饰器修饰,表示非必要传- 有
@Require
装饰器修饰,表示必要传参【注意:
@Require
装饰器是可以和其他装饰器叠加使用的】
@Entry@Componentstruct Index { @State message: string = \'parent\' build() { Row(){ Column() { Text(\'我是父组件\') // 调用子组件,必须传参,否则报错 ChildComponent({ message: \'父组件传递的值\', val: \'111\' }) } .height(\'100%\') } .width(\'100%\') }}// 自定义子组件@Componentstruct ChildComponent { // 子组件变量有@Require修饰,父组件调用子组件是必须传参 @Require @State message: string = \'Child\' @Require val: string = \'Child\' build() { Column() { Text(\'我是自定义子组件\') Text(this.message) } }}
7、@Reusable装饰器:组件复用
@Reusable
装饰器装饰任意自定义组件时,表示该自定义组件可以复用。被
@Reusable
修饰的组件,被从树上移除时,会放入缓存池内保存,当再次启用的时候,不会重新创建,而是从缓存池内获取。
7.1、不使用@Reusable
装饰
不使用@Reusable
修饰的组件,再次启动时会被重新创建。
【可以用aboutToAppear
生命周期钩子来观察,aboutToAppear
钩子函数会在组件创建的时候触发】
@Entry@Componentstruct Index { @State switch: boolean = true build() { Column({space: 10}) { Text(\'我是父组件\') // 模拟组件销毁创建 Button(\'模拟组件销毁创建\') .onClick(() => { this.switch = !this.switch }) if (this.switch) { // 调用子组件 ChildComponent() } } .height(\'100%\') .width(\'100%\') }} // 自定义子组件 @Component struct ChildComponent { // 观察组件是否被销毁 aboutToAppear(): void { console.log(\'aboutToAppear\', \'组件被创建了\') } build() { Column() { Text(\'我是自定义子组件\') } }}// 不停点击按钮的时候,控制台观察,每次ChildComponent组件显示时,aboutToAppear都会被触发。// 表示:ChildComponent组件每次消失时,都是直接被销毁,不会保留内容。ChildComponent组件每次展示都是一个被新创建的结构。
7.2、使用@Resuable
装饰
使用@Resuable
装饰的组件,再次启动时,不会被重新创建,而是从缓存池中获取。
【可以用aboutToAppear
生命周期钩子来观察】
@Entry@Componentstruct Index { @State switch: boolean = true build() { Column({ space: 10 }) { Text(\'我是父组件\') // 模拟组件销毁创建 Button(\'模拟组件销毁创建\') .onClick(() => { this.switch = !this.switch }) if (this.switch) { // 调用子组件 ChildComponent() } } .height(\'100%\') .width(\'100%\') }}// 自定义子组件@Reusable // 使用@Reusable装饰器,可以重复使用组件@Componentstruct ChildComponent { // 观察组件是否被销毁 aboutToAppear(): void { console.log(\'aboutToAppear\', \'组件被创建了\') } build() { Column() { Text(\'我是自定义子组件\') } }}// 不停点击按钮的时候,控制台观察,每次ChildComponent组件显示时,aboutToAppear不会被触发。// 表示:ChildComponent组件每次消失时,不会被被销毁,会进入缓存池。ChildComponent组件每次展示都是缓存池内保存的组件结构。
8、@AnimatableExtend装饰器:定义可动画属性
@AnimatableExtend
装饰器用于自定义可动画的属性方法,在这个属性方法中修改组件不可动画的属性。在动画执行过程时,通过逐帧回调函数修改不可动画属性值,让不可动画属性也能实现动画效果。也可通过逐帧回调函数修改可动画属性的值,实现逐帧布局的效果。
- 可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值可以使animation属性的动画效果生效,这个属性称为可动画属性。比如height、width、backgroundColor、translate属性,和Text组件的fontSize属性等。
- 不可动画属性:如果一个属性方法在animation属性前调用,改变这个属性的值不能使animation属性的动画效果生效,这个属性称为不可动画属性。比如Polyline组件的points属性等。
【注意:
@AnimatableExtend
仅支持定义在全局,不支持在组件内部定义。】【注意:
@AnimatableExtend
定义的函数参数类型必须为number类型或者实现 AnimatableArithmetic接口的自定义类型】【注意:使用
@AnimatableExtend
函数时,需要传入要修饰的UI组件】
// 定义可动画属性方法@AnimatableExtend(Text)function customAnimation(value: number) { .width(value)}@Entry@Componentstruct Index { @State message: string = \'Hello World Hello World Hello World Hello World\' @State widthNum: number = 100 build() { Column({ space: 10 }) { Text(this.message) .customAnimation(this.widthNum) // 调用并传参 .animation({ duration: 1000, curve: Curve.Ease }) Button(\'change\') .onClick(() => { this.widthNum = this.widthNum === 100 ? 200 : 100 }) } .height(\'100%\') .width(\'100%\') }}
本期分享到这里,下期分享V1、V2版本状态类装饰器。