HarmonyOS NEXT开发进阶(二):装饰器

文章目录

一、前言

什么是装饰器

装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如@Entry@Component@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件(同一个页面有且仅有一个入口组件 ),@State表示组件中的状态变量,状态变量变化会触发UI刷新(可以参考Vue中的data数据理解)。

装饰器具有以下特点:

  1. 装饰器是一个函数,这个函数仅在代码加载阶段执行一次。本质就是编译时执行的函数;

  2. 装饰器的语法是 @后跟一个函数或者一个执行后返回函数的表达式;

  3. 这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象;

  4. 装饰器有两个版本,一个是2014年通过的,一个是2022年通过的。ArkTS里使用的是2014年通过的;


使用要求

  1. @Observed 用于类,@ObjectLink 用于变量。
  2. @ObjectLink装饰的变量类型必须为类(class type)。
  3. 类要被@Observed装饰器所装饰。不支持简单类型参数,可以使用@Prop进行单向同步。
  4. @ObjectLink装饰的对象变量是不可变的,但可以修改对象里面变量的值。属性的改动是被允许的,当改动发生时,如果同一个对象被多个@ObjectLink变量所引用,那么所有拥有这些变量的自定义组件都会被通知去重新渲染。
  5. @ObjectLink装饰的变量不可设置默认值。必须让父组件中有一个由@State@Link@StorageLink@Provide@Consume所装饰变量参与的TS表达式进行初始化。
  6. @ObjectLink装饰的变量是私有变量,只能在组件内访问。

示例代码:

typescript 复制代码
@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装饰器具有以下特点:

  1. 支持多种类型:允许classnumberbooleanstring强类型的按值和按引用类型。允许这些强类型构成的数组,即Array。不允许object和any。
  2. 支持多实例:组件不同实例的内部状态数据独立。
  3. 内部私有:标记为@State的属性是私有变量,只能在组件内访问。
  4. 需要本地初始化:必须为所有@State变量分配初始值,将变量保持未初始化可能导致框架行为未定义。
  5. 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定@State状态属性的初始值。

注意❗️:在声明时必须指定类型和本地初始化。

@State装饰的变量与子组件中的@Prop装饰变量之间建立单向数据同步,与@Link@ObjectLink装饰变量之间建立双向数据同步。

@State@Link传参时,用$引用@State父组件变量的值,如:

typescript 复制代码
ComponentA({ highScore: $highScore })

@State@Prop传参时,用this.引用@State父组件变量的值,如:

typescript 复制代码
ComponentA({ highScore: this.highScore })

@State装饰的变量生命周期与其所属自定义组件的生命周期相同。

@State装饰器使用示例

typescript 复制代码
@Entry
@Component
struct MyComponent {
  // 定义一个状态变量
  @State count: number = 0;

  build() {
    Column() {
      // Text组件显示count的值
      Text(`count=${this.count}`)
      // Button是系统组件,添加一个点击事件,点击一次就给count + 1
      Button("点击+1")
        .onClick(() => {
          this.count++
        })
    }
  }
}

@State变量的传递规则

三、@Prop装饰器

@Prop装饰的变量可以和父组件建立单向的同步关系。

当父组件中的数据源更改时,与之相关的@Prop装饰的变量都会自动更新。

@Prop变量允许在本地修改,但修改后的变化不会同步回父组件。

备注:

@Prop@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但变量的更改不会通知给父组件,父组件变量的更改会同步到@prop装饰的变量,即@Prop属于单向数据绑定。

@Prop装饰器特点

  • @Prop装饰器不能在@Entry装饰的自定义组件中使用。因为@Entry修饰的组件是根组件,不存在父祖件。

  • @Prop装饰的状态量仅支持numberstringboolean等简单数据类型;

  • @Prop装饰的状态量在声明时必须指定类型,但可以不进行本地初始化。

  • @Prop装饰的状态量支持多个实例:一个组件中可以定义多个标有@Prop的属性;

  • @Prop装饰的状态量私有:仅支持组件内访问;

@Prop装饰器使用示例

typescript 复制代码
@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 + 1
      Button("点击+1")
        .onClick(() => {
          this.count++
        })
    }
  }
}

@Component
struct Child {
  // 定义一个@prop变量
  @Prop count: number

  build() {
    Text(`Child组件中count=${this.count}`)
  }
}

@Prop变量的传递规则

四、@Link装饰器

@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。

当父组件中的数据源更改时,与之相关的@Link装饰的变量都会自动更新。

@Link变量更改时,变化也会同步回父组件。

@Link装饰器特点

  • @Link装饰的变量不能在@Entry装饰的自定义组件中使用。因为@Entry修饰的组件是根组件,不存在父祖件。

  • @Link装饰的变量在声明时必须指定类型,不能进行本地初始化,并必须父组件初始化。

  • @Link装饰的变量私有:仅支持组件内访问;

  • @Link装饰的变量支持多种类型:@Link支持的数据类型与@State相同,即classnumberstringboolean或这些类型的数组;

创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化,@State变量可以通过​​'$'​​操作符创建引用。(参考下面的示例)

@Link装饰器使用示例

typescript 复制代码
@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 + 1
      Button("Parent组件中的点击+1")
        .onClick(() => {
          this.count++
        })
    }
  }
}

@Component
struct Child {
  // 定义一个@Link变量
  // 当Child中的count改变,Parent中的count也会同步改变
  @Link count: number

  build() {
    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 装饰器]

装饰器简单代码示例

typescript 复制代码
@ClassDecorator() // 类装饰器
class A {

  @PropertyDecorator() // 属性装饰器
  name: string;

  @MethodDecorator() // 方法装饰器
  fly(
    @ParameterDecorator() // 参数装饰器
    meters: number
  ) {
    // code
  }

  @AccessorDecorator() // 存取器装饰器
  get egg() {
    // code
  }
  set egg(e) {
    // code
  }
}

⚠️注意:

  • 构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是在装饰构造方法。
  • 装饰器只能用于类,要么应用于类的整体,要么应用于类的内部成员,不能用于独立的函数。
相关推荐
goto_w2 小时前
uniapp上使用webview与浏览器交互,支持三端(android、iOS、harmonyos next)
android·vue.js·ios·uni-app·harmonyos
别说我什么都不会17 小时前
ohos.net.http请求HttpResponse header中set-ccokie值被转成array类型
网络协议·harmonyos
码是生活18 小时前
鸿蒙开发排坑:解决 resourceManager.getRawFileContent() 获取文件内容为空问题
前端·harmonyos
鸿蒙场景化示例代码技术工程师18 小时前
基于Canvas实现选座功能鸿蒙示例代码
华为·harmonyos
小脑斧爱吃鱼鱼19 小时前
鸿蒙项目笔记(1)
笔记·学习·harmonyos
Debroon19 小时前
应华为 AI 医疗军团之战,各方动态和反应
人工智能·华为
鸿蒙布道师20 小时前
鸿蒙NEXT开发对象工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
zhang10620920 小时前
HarmonyOS 基础组件和基础布局的介绍
harmonyos·基础组件·基础布局
桃子酱紫君20 小时前
华为配置篇-BGP实验
开发语言·华为·php
马剑威(威哥爱编程)20 小时前
在HarmonyOS NEXT 开发中,如何指定一个号码,拉起系统拨号页面
华为·harmonyos·arkts