文章目录
- 一、前言
- 二、@State装饰器
- 三、@Prop装饰器
- 四、@Link装饰器
- 五、组合使用总结
- 六、拓展阅读
一、前言
什么是装饰器
装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如@Entry
、@Component
和@State
都是装饰器,@Component
表示自定义组件,@Entry
表示该自定义组件为入口组件(同一个页面有且仅有一个入口组件),@State
表示组件中的状态变量,状态变量变化会触发UI刷新(可以参考Vue中的data数据理解)。
装饰器具有以下特点:
装饰器是一个函数,这个函数仅在代码加载阶段执行一次。本质就是编译时执行的函数;
装饰器的语法是
@
后跟一个函数或者一个执行后返回函数的表达式;这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象;
装饰器有两个版本,一个是2014年通过的,一个是2022年通过的。ArkTS里使用的是2014年通过的;
使用要求
@Observed
用于类,@ObjectLink
用于变量。@ObjectLink
装饰的变量类型必须为类(class type)。- 类要被
@Observed
装饰器所装饰。不支持简单类型参数,可以使用@Prop
进行单向同步。 @ObjectLink
装饰的对象变量是不可变的,但可以修改对象里面变量的值。属性的改动是被允许的,当改动发生时,如果同一个对象被多个@ObjectLink
变量所引用,那么所有拥有这些变量的自定义组件都会被通知去重新渲染。@ObjectLink
装饰的变量不可设置默认值。必须让父组件中有一个由@State
、@Link
、@StorageLink
、@Provide
或@Consume
所装饰变量参与的TS表达式进行初始化。@ObjectLink
装饰的变量是私有变量,只能在组件内访问。
示例代码:
@Observed
class ClassA {public name : string;public c: number;constructor(c: number, name: string = '') {this.name = name;this.c = c;}
}class ClassB {public a: ClassA;constructor(a: ClassA) {this.a = a;}
}@Component
struct ViewA {label : string = "ep1";@ObjectLink a : ClassA;build() {Column() {Text(`ViewA [${this.label}]: a.c=${this.a.c}`).fontSize(20)Button(`+1`).width(100).margin(2).onClick(() => {this.a.c += 1;})Button(`reset`).width(100).margin(2).onClick(() => {this.a = new ClassA(0); // 错误:ObjectLink装饰的变量a是不可变的})}}
}@Entry
@Component
struct ViewB {@State b : ClassB = new ClassB(new ClassA(10));build() {Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Center}) {ViewA({label: "ViewA #1", a: this.b.a})ViewA({label: "ViewA #2", a: this.b.a})Button(`ViewB: this.b.a.c += 1` ).width(320).margin(4).onClick(() => {this.b.a.c += 1;})Button(`ViewB: this.b.a = new ClassA(0)`).width(240).margin(4).onClick(() => {this.b.a = new ClassA(0);})Button(`ViewB: this.b = new ClassB(ClassA(0))`).width(240).margin(4).onClick(() => {this.b = new ClassB(new ClassA(0));})}}
}
二、@State装饰器
@State
装饰的变量,或称为状态变量,一旦拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。
在状态变量相关装饰器中,@State
是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。
@State
装饰器具有以下特点:
- 支持多种类型:允许
class
、number
、boolean
、string
强类型的按值和按引用类型。允许这些强类型构成的数组,即Array
。不允许object和any。 - 支持多实例:组件不同实例的内部状态数据独立。
- 内部私有:标记为
@State
的属性是私有变量,只能在组件内访问。 - 需要本地初始化:必须为所有
@State
变量分配初始值,将变量保持未初始化可能导致框架行为未定义。 - 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定
@State
状态属性的初始值。
注意❗️:在声明时必须指定类型和本地初始化。
@State
装饰的变量与子组件中的@Prop
装饰变量之间建立单向数据同步,与@Link
、@ObjectLink
装饰变量之间建立双向数据同步。
@State
向@Link
传参时,用$
引用@State
父组件变量的值,如:
ComponentA({ highScore: $highScore })
@State
向@Prop
传参时,用this.
引用@State
父组件变量的值,如:
ComponentA({ highScore: this.highScore })
@State
装饰的变量生命周期与其所属自定义组件的生命周期相同。
@State
装饰器使用示例
@Entry
@Component
struct MyComponent {// 定义一个状态变量@State count: number = 0;build() {Column() {// Text组件显示count的值Text(`count=${this.count}`)// Button是系统组件,添加一个点击事件,点击一次就给count + 1Button("点击+1").onClick(() => {this.count++})}}
}
@State变量的传递规则
三、@Prop装饰器
@Prop
装饰的变量可以和父组件建立单向的同步关系。
当父组件中的数据源更改时,与之相关的@Prop
装饰的变量都会自动更新。
@Prop
变量允许在本地修改,但修改后的变化不会同步回父组件。
备注:
@Prop
与@State
有相同的语义,但初始化方式不同。@Prop
装饰的变量必须使用其父组件提供的@State
变量进行初始化,允许组件内部修改@Prop
变量,但变量的更改不会通知给父组件,父组件变量的更改会同步到@prop
装饰的变量,即@Prop
属于单向数据绑定。
@Prop装饰器特点
-
@Prop
装饰器不能在@Entry
装饰的自定义组件中使用。因为@Entry
修饰的组件是根组件,不存在父祖件。 -
@Prop
装饰的状态量仅支持number
、string
、boolean
等简单数据类型; -
@Prop
装饰的状态量在声明时必须指定类型,但可以不进行本地初始化。 -
@Prop
装饰的状态量支持多个实例:一个组件中可以定义多个标有@Prop
的属性; -
@Prop
装饰的状态量私有:仅支持组件内访问;
@Prop装饰器使用示例
@Entry
@Component
struct Parent {// 定义一个状态变量@State count: number = 0;build() {Column() {// 使用自定义Child组件,并用Parent中count初始化Child中的count// 当Parent中count改变时,Child中的count也会同步修改Child({ count: this.count })Text(`Parent组件中count=${this.count}`)// Button是系统组件,添加一个点击事件,点击一次就给count + 1Button("点击+1").onClick(() => {this.count++})}}
}@Component
struct Child {// 定义一个@prop变量@Prop count: numberbuild() {Text(`Child组件中count=${this.count}`)}
}
@Prop变量的传递规则
四、@Link装饰器
@Link
装饰的变量与其父组件中对应的数据源建立双向数据绑定。
当父组件中的数据源更改时,与之相关的@Link
装饰的变量都会自动更新。
@Link
变量更改时,变化也会同步回父组件。
@Link装饰器特点
-
@Link
装饰的变量不能在@Entry
装饰的自定义组件中使用。因为@Entry
修饰的组件是根组件,不存在父祖件。 -
@Link
装饰的变量在声明时必须指定类型,不能进行本地初始化,并必须父组件初始化。 -
@Link
装饰的变量私有:仅支持组件内访问; -
@Link
装饰的变量支持多种类型:@Link
支持的数据类型与@State
相同,即class
、number
、string
、boolean
或这些类型的数组;
创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化,@State变量可以通过’$'操作符创建引用。(参考下面的示例)
@Link装饰器使用示例
@Entry
@Component
struct Parent {// 定义一个状态变量@State count: number = 0;build() {Column() {// 使用自定义Child组件,并用Parent中的count初始化Child中的count// 给@Link变量初始化,必须使用$// 当Parent中的count改变时,Child中的count也会同步改变Child({count: $count})Text(`Parent组件中count=${this.count}`)// Button是系统组件,添加一个点击事件,点击一次就给count + 1Button("Parent组件中的点击+1").onClick(() => {this.count++})}}
}@Component
struct Child {// 定义一个@Link变量// 当Child中的count改变,Parent中的count也会同步改变@Link count: numberbuild() {Column() {Text(`Child组件中count=${this.count}`)Button("Child组件中的点击+1").onClick(() => {this.count++})}}
}
@Link变量的传递规则
五、组合使用总结
如果父组件A中有个变量@state
的变量A,子组件B中@Prop
变量B,子组件C中@Link
变量C;
当子组件B 和 子组件C 同时在父组件A中使用的时候:
-
当变量A变化的时候–变量B 和变量C 都会变化;
-
当变量C 变化的时候–变量A 和B 都会变化 ;
-
当变量B变化的时候–A 和C 都不会变化;
装饰器分类
本文介绍的是2014年通过的装饰器版本。如果想了解最新版的装饰器,请移步 [TypeScript 装饰器]。
装饰器简单代码示例
@ClassDecorator() // 类装饰器
class A {@PropertyDecorator() // 属性装饰器name: string;@MethodDecorator() // 方法装饰器fly(@ParameterDecorator() // 参数装饰器meters: number) {// code}@AccessorDecorator() // 存取器装饰器get egg() {// code}set egg(e) {// code}
}
⚠️注意:
- 构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是在装饰构造方法。
- 装饰器只能用于类,要么应用于类的整体,要么应用于类的内部成员,不能用于独立的函数。
六、拓展阅读
- 《HarmonyOS NEXT开发进阶》