HarmonyOS相关装饰器

442 阅读5分钟

一,什么是装饰器,有什么作用

在鸿蒙(HarmonyOS)开发中,装饰器是一种特殊的声明,可以用于修改类、方法、属性或参数的行为。装饰器的主要作用是提供一种简洁的语法,用于在不修改原有代码结构的情况下,添加额外的功能或修改现有功能。

装饰器的基本概念和作用如下:

1.基本概念

  • 装饰器:装饰器是一种特殊的声明,用于修改类、方法、属性或参数的行为。装饰器通常以“@”符号开头,后面跟随装饰器的名称和一对圆括号。
  • 状态管理装饰器:用于管理组件和应用的状态 例如,@State用于声明状态变量,@Observed用于观察类属性的变化。
  • 嵌套装饰器:多个装饰器可以组合使用,形成嵌套装饰器。例如,@ObservedV2和@Trace装饰器需要配合使用

2.作用

  • 状态管理:装饰器在状态管理中起到关键作用。例如,@State装饰器用于声明状态变量,当状态变量改变时,UI会重新渲染@Observed和@Trace装饰器用于观察类属性的变化,从而触发UI刷新 
  • 组件间数据传递:装饰器可以用于在组件之间传递数据例如,@Event装饰器用于子组件向父组件传递数据,实现数据的同步更新
  • 类和属性观测:装饰器可以增强对类和属性变化的观测能力 例如,@ObservedV2和@Trace装饰器可以观测嵌套类中属性的变化,从而触发UI刷新

通过使用装饰器,开发者可以更灵活地管理组件状态、实现数据传递和观测类属性的变化,从而提高代码的可维护性复用性

二,根据各自作用将装饰器分为以下几种类型(仅接触到的)

1.装饰组件

(1)Entry 定义一个页面 本质也是一个组件。

在HarmonyOSArkTS开发中,@entry装饰器通常用于标识应用程序的入口点。它主要用于在应用启动时执行特定的初始化操作。例如,当应用启动时,系统会自动寻找被@entry装饰的组件或方法,并执行其中的逻辑。

(2)Component 定义一个可复用组件

例:

image.png

@Component  //自定义了按钮一个组件
struct MyButton {
  build() {
    Row() {
      Button("按钮")
        .type(ButtonType.Normal)
        .borderRadius(3)
      Text()
        .width(10)
        .height(10)
        .backgroundColor("#007DFE")
        .translate({ x: -5 })
        .rotate({ angle: 45 })
    }
  }
}
@Entry
@Component
struct Index {
  build() {
    Column({space:10}) {
      MyButton()    //引用自己定义的按钮组件
      MyButton()
      MyButton()
    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.Orange)
  }
}

2.对组件,样式及事件进行封装

(1)Builder 最常用可以封装:样式 结构 事件 可传参

例:

image.png

@Entry
@Component
struct Index {
  @Builder   //定义自定义构建函数,函数名为:component
  component(photo: string, text: string) {
    GridItem(){
      Column({ space: 10 }) {
        Image(photo)
          .width(60)
        Text(text)
          .fontWeight(700)
      }
      .margin(20)
    }

  }

  build() {
    Column() {
      Grid(){
         //使用该函数,传参渲染页面
        this.component('/imgs//ic_taobao_09.png', '新品') 
        this.component('/imgs//ic_taobao_08.png', '药品')
        this.component('/imgs//ic_taobao_07.png', '阿里')
        this.component('/imgs//ic_taobao_06.png', '服饰')
        this.component('/imgs//ic_taobao_05.png', '蔬菜')
        this.component('/imgs//ic_taobao_04.png', '快递')
      }
    }
    .width('100%')
    .height(500)
  }
}

(2)Style 仅限于封装通用属性和事件 不可传参

例:

image.png

@Styles
function commonStyles() {
  .width(100)
  .height(50)
  .backgroundColor('#ff20e359')
}

@Entry
@Component
struct Index {
  @Styles
  commonStyles1(){
    .borderRadius(10)
  }

  build() {
    Column({ space: 20 }) {
      Column()
        .commonStyles()
        .commonStyles1()
      Column()
        .commonStyles()
      Column()
        .commonStyles()
    }
.width('100%')
  }
}

(3)Extends 可传参 抽取特定组件样式进行封装

例:

image.png

@Extend(Text)
function textStyle(num:number,msg?:string){
  .fontSize(num)
  .fontWeight(700)
  .backgroundColor(Color.Orange)
  .onClick(()=>{
    AlertDialog.show({message:msg})
  })
}
@Entry
@Component
struct Index {
  build() {
    Column({space:30}){
      Text('刘备')
        .textStyle(20,'牢大')
      Text('关羽')
        .textStyle(16,'牢二')
      Text('张飞')
        .textStyle(12,'牢三')
    }
    .height('100%')
    .width('100%')
  }
}

(4)BuilderParam 该装饰器用于声明任意UI描述的一个元素,类似slot占位符。允许外部传递UI

例:单个@BuilderParam参数

image.png

@Component
struct mycomp {

  @Builder hbuilder(){
    Text('可替换组件')
  }
  
  @BuilderParam
  con:()=>void=this.hbuilder
  build() {
    Column(){
      Text('=========================')
      this.con()  //指定插入位置
      Text('=========================')
    }
  }
}
@Entry
@Component
struct Index {
  build() {
    Column() {
      mycomp()

      mycomp() {
        Button('插入的按钮')
      }

      mycomp() {
        TextInput()
      }

      mycomp() {
        Image($r('app.media.app_icon'))
          .width(60)
      }

    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.Orange)
  }
}

例:多个@BuilderParam 参数

image.png

@Component
struct mycomp {
  // 设置默认 的 Builder,避免外部不传入
  @Builder
  hbuilder() {
    Text('可替换组件')
  }
    // 由外部传入 UI
  @BuilderParam
  start: () => void = this.hbuilder
  @BuilderParam
  middle: () => void = this.hbuilder
  @BuilderParam
  end: () => void = this.hbuilder

  build() {
    Column() {
      Row() {
        this.start()
        this.middle()
        this.end()
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .width('100%')
    }

  }
}

@Entry
@Component
struct Index {
  @Builder
  head() {
    Text('首页')
  }

  @Builder
  middlend() {
    Text('中间')
  }

  @Builder
  end() {
    Text('结尾')
  }

  build() {
    Column() {
      mycomp({ start: this.head, middle: this.middlend, end: this.end })

    }
    .padding(20)
    .height('100%')
    .width('100%')
    .backgroundColor(Color.Orange)
  }
}

3.组件的状态管理

(1)State 用于声明组件内的状态变量 状态变量改变促使UI改变

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性。例子如下。声明ClassA和Model类

(2).1 Prop 父->子单项传递 修改值时注意统一数据源

  • 修改父组件数据,会同步更新子组件
  • 修改子组件@Prop 修饰的数据,子组件 UI 更新,但会被父组件状态更新覆盖
  • 通过回调函数的方式修改父组件的数据,然后触发@Prop数据的更新

需求如下: 点击父组件图片,子组件可以显示出来相应的图片

PixPin_2024-09-03_21-12-16.gif

@Component
struct right {
  @Prop url: string = ''              //定义容器来接收父组件传来的数据

  build() {
    Column() {
      Image($r(this.url))         //子组件操作接收到的数据
        .onClick(() => {
          console.log('value1', this.url)     
        })
    }
    .backgroundColor(Color.Green)
    .width(200)
    .height(200)
  }
}

@Entry
@Component
struct Index {
  list: string[] = ['app.media.111', 'app.media.2', 'app.media.3', 'app.media.4']
  @State url: string = ''

  build() {
    Column() {
      Row() {
        Column() {
          Text('父组件')
          ForEach(this.list, (item: string) => {
            Image($r(item))
              .width(100)
              .height(100)
              .margin(20)
              .onClick(() => {
                this.url = item
                console.log('value', this.url)
              })
          })
        }
        .border({ width: 2 })
        .width(100)

        Column() {
          Text('子组件')
          right({ url: this.url })  //连接数据纽带:子组件的url=父组件中点击的url
        }
        .border({ width: 2 })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

    }
    .padding(10)
    .height('100%')
    .width('100%')
    .backgroundColor(Color.Orange)
  }
}

(2).2 子->父 子组件向父组件传递数据通过父组件传递函数,子组件调用函数的方式实现,否则子组件自己操作会导致子组件数据变化,父组件值仍然不会改变,就是两个数据源。类似于:现如今微信,支付宝等都可以支出,而钱都来自银行,试想以下单独修改子组件(支出微信),父组件(银行)值却没有改变,那岂不是可以比肩马云。

image.png 案例同上,由此解决办法如下:
@Component
struct left {
  Url: string = ''
  /////////////////////第一步:准备接收父亲传过来的函数
geturl:()=>void=()=>{

}
  build() {
    Column() {
      Image($r(this.Url))
        .width(100)
        .onClick(()=>{
          this.geturl()         ///////////第二步:调用
          console.log('value',this.Url)
        })
    }
  }
}
@Entry
@Component
struct Index {
  list: string[] = ['app.media.111', 'app.media.2', 'app.media.3', 'app.media.4']
  @State url: string = ''

  build() {
    Column() {
      Row() {
        Column() {
          ForEach(this.list, (item: string) => {
          /////第三步:geturl是父组件定义的修改数据的函数,Url,geturl是子组件接收父组件传递数据的容器
            left({ Url: item ,geturl:()=>{  
              this.url=item
            }})
              .margin(20)
          })
        }
        .width(100)
        Image($r(this.url))
          .onClick(()=>{
            console.log('value',this.url)
          })
          .width(200)
          .height(200)
          .backgroundColor(Color.Red)
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)

    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.Orange)
  }
}

拓展:this指向问题

  • 在哪里定义的箭头函数,箭头函数中this 永远指向谁
  • 普通函数this 是谁调用, this指向谁

(3)Link 双向数据传递 仅修饰简单数据类型 不适用于ForEach 适用引用数据类型 不适用深层次引用数据类型

例:

PixPin_2024-09-04_09-51-16.gif

@Component
struct MyButton {
  @Link
  num: number       //////第一步定义接收容器

  build() {
    Column() {
      Button(`儿子的数据 ${this.num}`)       /////第二步调用
        .onClick(() => {
          this.num++
        })
    }
  }
}
@Entry
@Component
struct Index {
  @State
  num: number = 100

  build() {
    Column({ space: 10 }) {

      Button(`父亲的数据 ${this.num}`)
        .onClick(() => {
          this.num++
        })
      //   ====
      MyButton({ num: this.num })      //////第三步:父子连接通道

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

(4)Observed 和 ObjectLink 可直接修改深层次属性 按需优化:数据改变,整个组件不需要重新加载,仅加载数据的改变,可以很大程度降低内存消耗

作用:之前了解的修改嵌套对象的写法,会让页面刷新,降低用户体验。为了解决这个问题,可以通过 @Observed 与 @ObjectLink 这两个装饰器解决

使用步骤:

  • interface 改为 类 class 并使用 @Observed 修饰
  • 通过 new 的方式完成数据的创建(通过 new 的方式来监测数据的改变
  • 状态修饰符改为 @ObjectLink
    • 在父组件修改数据:不需要 splice
    • 在子组件修改数据

举例

  • 优化前:*

recording.gif

class Person {
  num: number = 100
  avatar: string = 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63'
}

@Component
struct MyItem {
  @Prop
  item: Person
  add = () => {
  }

  build() {
    Row() {
      Image(this.item.avatar)
        .width(60)
      Text(this.item.num.toString())
    }
    .onClick(this.add)

  }
}


@Entry
@Component
struct Index {
  @State
  personList: Person[] = [
    new Person(),
    new Person()
  ]

  build() {
    Column() {
      ForEach(this.personList, (item: Person, index: number) => {
        MyItem({
          item: item, add: () => {
            this.personList[index].num++
                           //由于是深层次属性,页面更新需要用到数组API:splice,不适用于@link
            this.personList.splice(index,1,item)
            console.log('value',this.personList[index].num)
          }
        })
      })

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}
  • 优化后:*

recording.gif

// 类型 只能使用class
@Observed     //仅适用于Class类
class Person {
  num: number = 100
  avatar: string = 'https://picx.zhimg.com/027729d02bdf060e24973c3726fea9da_l.jpg?source=06d4cd63'
}

@Component
struct MyItem {
  @ObjectLink    
  item: Person
  add = () => {
  }

  build() {
    Row() {
      Image(this.item.avatar)
        .width(60)
      Text(this.item.num.toString())
    }
    .onClick(this.add)

  }
}


@Entry
@Component
struct Index {
  @State
  personList: Person[] = [
    new Person(),      //建立对象,实例化
    new Person()
  ]

  build() {
    Column() {
      ForEach(this.personList, (item: Person, index: number) => {
        MyItem({
          item: item, add: () => {
            this.personList[index].num++     //无需调用数组API
          }
        })
      })

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

(5)Provice Consume 爷孙组件 跨组件传参

例:

recording.gif

interface Person {
  num: number
  changeNum: () => void
}
@Entry
@Component
struct Index {
////////提供给孙子的属性,方法
  @Provide
  person: Person = {
    num: 200, changeNum: () => {
      this.person.num++
    }
  }

  build() {
    Column() {
      Text(`爷爷 ${this.person.num}`)
        .fontSize(40)
        .onClick(() => {
          this.person.num++
        })
      AAA()
    }
    .width("100%")
    .height(300)
    .border({
      width: 5,
      color: Color.Yellow
    })
    .justifyContent(FlexAlign.Center)
  }
}
@Component
struct AAA {
  build() {
    Column() {
      BBB()
    }
    .width("100%")
    .height(200)
    .border({
      width: 5,
      color: Color.Blue
    })
    .justifyContent(FlexAlign.Center)
  }
}
@Component
struct BBB {
  // 接收爷爷传来的属性方法
  @Consume
  person: Person

  build() {
    Column() {
      Text(`孙子组件 ${this.person.num}`)
        .fontSize(20)
        .onClick(this.person.changeNum)

    }
    .width("100%")
    .height(100)
    .border({
      width: 5,
      color: Color.Red
    })
  }
}

(6)Watch 监听某个数据 发生改变, 然后做出其他的业务处理。如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。

  • @State、@Prop、@Link 等装饰器在 @Watch 装饰之前
  • Watch无法单独使用,必须配合状态装饰器,比如@State、@Prop、@Link 基本语法:
@State
@Watch('方法名')
info:string=''

方法名(){
  // 数据改变之后会触发
}
  • 只能用普通方法,箭头函数会报错,不考虑

recording.gif

注意:

  • 初始有两个偶数,所以点击第一下加2—3的值,因为下一个可能是奇数
  • Data.now()用的时间戳添加给数组(类似于随机数),是不是偶数有不确定性

时间戳:是从1970年1月1日(UTC/GMT的午夜开始所经过的秒数(不考虑闰秒),用于表示一个时间点

@Entry
@Component
struct Index {
  @State
  
  @Watch('GetLength')
  list:number[]=[3,1,16,2]
  @State num:number=0
  
  GetLength(){
    this.num=this.list.filter(n=>n%2==0).length
  }
  
  build() {
    Column({space:20}){
      Button(`数组长度${this.list.length}`)
        .fontSize(30)
        .onClick(()=>{
          this.list.push(Date.now())
          console.log('value',Date.now())
        })
      Text(`数组偶数个数 ${this.num}`)
        .fontSize(30)
    }
    .height('100%')
    .width('100%')
    .backgroundColor(Color.Orange)
  }
}

拓展:@Link 或者 @Observed 和 @objectLink使用场景

  1. @Link 用不了 无法和foreach中item关联

  2. @Observed 和 @objectLink

    1. class
    2. 拆分成组件
  3. 任何修饰器 都做不到 儿子修改父亲 深层次数据 , 两个改变都不到

    1. 数据改变
    2. UI改变
  4. @objectLink 能做到 儿子修改父亲 深层次的数据

    1. 数据变
    2. ui不变
  5. 真要做到 儿子修改父亲 深层次数 ui变化和数据变化

    1. this.list.splice
    2. this.list[index] = xxxx