在ArkUI中,各种装饰器总结

371 阅读8分钟

ArkUI提供了多种装饰器,通过使用这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:

  • 管理组件拥有状态的装饰器: 组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
  • 管理应用拥有状态的装饰器: 应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理

1.@Entry

@Entry 修饰器的主要作用是标记一个类为ArkUI应用的入口组件。在ArkUI或HarmonyOS应用开发中,每个页面组件或都对应一个类,而这个类代表的@Entry组件是修饰应用的器入口就是点用来。告诉这框架意呀,当应用启动时,系统会首先加载并渲染这个被@Entry修饰的组件。

@Entry
@Component
struct 组件名{
      build(){
      
      }
}

2.@Component

@Component 修饰器(Decorator)通常用于定义一个类为ArkUI的组件。一个ets可以包含很多的组件@Component,并且他们之间可以通过各种修饰器进行相互传参数

@Entry
@Component
struct 组件A{
      build(){
      
      }
}

@Component 
struct 组件B{
    build(){
    
    }
}

3.@Extend

通过Extend可以拓展组件的样式,事件实现复用的效果

语法:

// 1. 定义在全局
@Extend(组件名) 
function functionName(参数1....) {
  .属性()
  .事件(()=>{})
}


// 2. 使用
组件名(){}
  .functionName(参数1...)

那么通过以下实例来进行讲解

没有进行@Extend抽取前的代码

Swiper() {
  Text('0')
    .textAlign(TextAlign.Center)
    .backgroundColor(Color.Red)
    .fontColor(Color.White)
    .fontSize(30)
    .onClick(() => {
        AlertDialog.show({
          message: '轮播图 1'
        })
      })
  Text('1')
    .textAlign(TextAlign.Center)
    .backgroundColor(Color.Green)
    .fontColor(Color.White)
    .fontSize(30)
    .onClick(() => {
        AlertDialog.show({
          message: '轮播图 2'
        })
      })
  Text('2')
    .textAlign(TextAlign.Center)
    .backgroundColor(Color.Blue)
    .fontColor(Color.White)
    .fontSize(30)
    .onClick(() => {
        AlertDialog.show({
          message: '轮播图 3'
        })
      })
}

通过@Extend抽取Text的属性


//1.定义,抽取
@Extend(Text)
function texteExtend(){
    .textAlign(TextAlign.Center)
    .backgroundColor(Color.Blue)
    .fontColor(Color.White)
    .fontSize(30)
    .onClick(() => {
        AlertDialog.show({
          message: '轮播图 3'
        })
      })
}
@Entry
@Component
struct Index{
     build(){
       Swiper() {
          Text('0')
            .textExtend(Color.Red,'轮播图 1')
          Text('1')
            .textExtend(Color.Green,'轮播图 2')
          Text('2')
            .textExtend(Color.Blue,'轮播图 3')
}       
   }
}

注意:它可以对某一个组件的属性进行抽取封装,如果当有其中的一个属性的值不一样时,就可以采取传参的方式进行它的改变。但是它只能定义在全局

4.@Styles

不同于前面的@Extend用来给某个组件进行拓展封装,@Styles可以抽取通用的事件和属性给多个组件来使用。

语法:

// 全局定义
@Styles function functionName() {
  .通用属性()
  .通用事件(()=>{})
}


@Component
struct FancyUse {
  // 在组件内定义
  @Styles fancy() {
      .通用属性()
      .通用事件(()=>{})
  }
}


// 使用
组件().fancy()
组件().functionName()

举例:

没有进行抽取的代码:

@Entry
@Component
struct Index {
  @State message: string = '@styles';
  @State bgColor: ResourceColor = Color.Gray

  build() {
    Column({ space: 10 }) {
      Text(this.message)
        .width(100)
        .height(100)
        .backgroundColor(this.bgColor)
        .onClick(() => {
          this.bgColor = Color.Orange
        })

      Column() {
      }
      .width(100)
      .height(100)
      .backgroundColor(this.bgColor)
      .onClick(() => {
        this.bgColor = Color.Orange
      })

      Button('按钮')
        .width(100)
        .height(100)
        .backgroundColor(this.bgColor)
        .onClick(() => {
          this.bgColor = Color.Orange
        })
    }
    .width('100%')
    .height('100%')
  }  
}

使用@Style抽取后

//全局定义写法
@Styles
function globalSize() {
  .width(100)
  .height(100)
}


@Entry
@Component
struct Index {
  @State message: string = '@styles';
  @State bgColor: ResourceColor = Color.Gray

  build() {
    Column({ space: 10 }) {
      Text(this.message)
        .width(100)
        .height(100)
        .backgroundColor(this.bgColor)
        .onClick(() => {
          this.bgColor = Color.Orange
        })

      Column() {
      }
      
      //使用
      .globalSize()
      .sizeAndColorFancy()
    

      Button('按钮')
        .width(100)
        .height(100)
        .backgroundColor(this.bgColor)
        .onClick(() => {
          this.bgColor = Color.Orange
        })
    }
    .globalSize()
  }

  //局部定义写法
  @Styles
  sizeAndColorFancy() {
    .backgroundColor(this.bgColor)
    .onClick(() => {
      this.bgColor = Color.Orange
    })
  }
}

注意:它只能抽取一些通用的属性和通用的事件,对于一些组件的私有属性它不能进行抽取,而且@Style不能进行传参,它有全局写法和局部写法两种。如果在组件中设置了@Style包含的通用属性的话,会将@Style定义的属性覆盖

5. @Builder

如果连结果也需要进行抽取的话,就可以使用@Builder来进行抽取。

语法:

// 自定义 全局 构建函数
@Builder function MyGlobalBuilderFunction(param1,param2...) {
  // 结构、属性、事件放这里
}
// 使用
MyGlobalBuilderFunction(param1,param2...)

// 自定义 组件内 构建函数
@Builder MyBuilderFunction( param1,param2...) {
  // 结构、属性、事件放这里
}
// 使用
this.MyBuilderFunction(param1,param2...)

没有没有抽取前的代码:

@Entry
@Component
struct Index {
  @State message: string = '@Builder';

  build() {
    Column({ space: 20 }) {
      Text(this.message)
        .fontSize(30)

      Row() {
        Row() {
          Column({ space: 10 }) {
            Image($r('app.media.ic_reuse_01'))
              .width('80%')
            Text('阿里拍卖')

          }
          .width('25%')
          .onClick(() => {
            AlertDialog.show({
              message: '点了 阿里拍卖'
            })
          })

          Column({ space: 10 }) {
            Image($r('app.media.ic_reuse_02'))
              .width('80%')
            Text('菜鸟')

          }
          .width('25%')
          .onClick(() => {
            AlertDialog.show({
              message: '点了 菜鸟'
            })
          })

          Column({ space: 10 }) {
            Image($r('app.media.ic_reuse_03'))
              .width('80%')
            Text('芭芭农场')

          }
          .width('25%')
          .onClick(() => {
            AlertDialog.show({
              message: '点了 芭芭农场'
            })
          })

          Column({ space: 10 }) {
            Image($r('app.media.ic_reuse_04'))
              .width('80%')
            Text('阿里药房')

          }
          .width('25%')
          .onClick(() => {
            AlertDialog.show({
              message: '点了 阿里药房'
            })
          })
        }
      }
    }
    .width('100%')
    .height('100%')
  }
}

进行抽取后:


// 1.全局 Builder
@Builder
function navItem(icon: ResourceStr, text: string) {
  Column({ space: 10 }) {
    Image(icon)
      .width('80%');
    Text(text);
  }
  .width('25%')
  .onClick(() => {
    AlertDialog.show({
      message: '点了' + text
    })
  })
}

@Entry
@Component
struct Index {
  @State message: string = '@Builder';

  build() {
    Column({ space: 20 }) {
      Text(this.message)
        .fontSize(30)

      Row() {
       
       
       //3. 这两个使用全局的
        navItem($r('app.media.ic_reuse_01'), '阿里拍卖')
        navItem($r('app.media.ic_reuse_02'), '菜鸟')
        
        
        //4. 这两个使用本地的
        this.navItem($r('app.media.ic_reuse_03'), '芭芭农场')
        this.navItem($r('app.media.ic_reuse_04'), '阿里药房')
      }
    }
    .width('100%')
    .height('100%')
  }

  // 2.局部 Builder
  @Builder
  navItem(icon: ResourceStr, text: string) {
    Column({ space: 10 }) {
      Image(icon)
        .width('80%');
      Text(text);
    }
    .width('25%')
    .onClick(() => {
      AlertDialog.show({
        message: '点了' + text
      })
    })
  }
}

注意:@Builder有两种写法,可以写在全局以及局部,它可以进行抽取结构,事件,属性,样式,功能强大,也可以进行传参,开发中常用。

6.@State

在ArkTS中,被@State修饰的变量称为状态变量,它的更改可以引起UI的刷新。

// for simple type
@State count: number = 0;
// value changing can be observed
this.count = 1;

7.@BuilderParam

@BuilderParam该装饰器用于声明任意UI描述的一个元素,类似slot占位符。总而言之就是自定义组件允许外部传递UI

7.1.单个@BuilderParam参数

举个例子:

@Component
struct mybutton {
  @Builder
  // 1.占位符构建函数,里面的内容可写可不写
  IsShow() {

  }

  @BuilderParam
  // 2.() => void表示的是一个函数类型,abc是自己定义的一个函数名称
  abc: () => void = this.IsShow

  build() {
    Column() {

      Text('===================')
      // 3.这里的这个this.abc()是拿来给下面的mybutton()里面的内容占位的
      this.abc()

      Text('==================')


    }.height(300)
    .width(300)
    .border({
      width:10,
      color:Color.Black
    })
    .borderRadius(150)
  }
}


@Entry
@Component
struct Index {
  build() {
    Column() {
      mybutton(){
        // 4.因为上面已经有this.abc()来给自己占位了,所以这里面的东西就能从主组件进行传入
        Image($r('app.media.app_icon'))
          .width(30)
        Text('我的东西')
      }


    }.width('100%')
    .height('100%')
  }
}

使用尾随闭包的方式传入:

  • 组件内有且仅有一个使用 @BuilderParam 装饰的属性,即可使用尾随闭包
  • 内容直接在 {} 传入即可

注意:

  • 此场景下自定义组件不支持使用通用属性。**

7.2.多个@BuilderParam 参数

子组件有多个BuilderParam,必须通过参数的方式来传入

@Component
struct mySelf {
  @Builder
  //占位符构建函数,里面的内容可写可不写
  IsShow() {
    Text('占位符')
  }

  @BuilderParam
  //() => void表示的是一个函数类型,abc是自己定义的一个名称
  a: () => void = this.IsShow
  @BuilderParam
  //() => void表示的是一个函数类型,abc是自己定义的一个名称
  b: () => void = this.IsShow
  @BuilderParam
  //() => void表示的是一个函数类型,abc是自己定义的一个名称
  c: () => void = this.IsShow

  //子组件的内容
  build() {
    Row() {
      this.a()
      this.b()
      this.c()
    }.backgroundColor(Color.White)
    .justifyContent(FlexAlign.SpaceBetween)
    .width('100%')
    .padding(20)

  }
}


@Entry
@Component
struct Index {
  @Builder
  a1() {
    Text('返回')
      .fontSize(30)
  }
  @Builder
  b1() {
    Text('首页')
      .fontSize(30)
  }
  @Builder
  c1() {
    Text('菜单')
      .fontSize(30)
  }

  //父组件的内容
  build() {
    Row() {
      mySelf({
        a:this.a1,
        b:this.b1,
        c:this.c1
      })

    }.width('100%')
    .height('100%')

    .backgroundColor(Color.Gray)
  }
}

说明:它需要与@Builder配合使用

8.@Prop

#@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop 装饰的变量是可变的,但是变化不会同步回其父组件。

8.1.简单类型

对于简单类型 string、number、boolean、enum,可以直接在子组件里面进行数据修改,而且会引起UI改变以及父组件的数据更改。但是要同步给父组件需要使用回调函数(箭头函数),否则只是父亲的单方向同步给子组件

@Component
struct SonCom {
  @Prop info: string
  changeInfo = (newInfo: string) => {
  }

  build() {
    Button('info:' + this.info)
      .onClick(() => {
        this.changeInfo('改啦')
      })
  }
}

@Entry
@Component
struct FatherCom {
  @State info: string = '么么哒'

  build() {
    Column() {
      Text(this.info)
      SonCom({
        info: this.info,
        changeInfo: (newInfo: string) => {
          this.info = newInfo
        }
      })
    }
    .padding(20)
    .backgroundColor(Color.Orange)
  }
}

8.2.引用数据类型(复杂类型)

复杂类型的做法类似,通过回调函数将需要修改的数据传递给父组件即可

interface User {
  name: string
  age: number
}

@Entry
@Component
struct Index {
  @State
  userInfo: User = {
    name: 'jack',
    age: 18
  }

  build() {
    Column({ space: 20 }) {
      Text('父组件')
        .fontSize(30)
      Text('用户名:' + this.userInfo.name)
        .white()
        .onClick(() => {
          this.userInfo.name = 'rose'
        })
      Text('年龄:' + this.userInfo.age)
        .white()
        .onClick(() => {
          this.userInfo.age++
        })

      Child({
        user: this.userInfo,
        userChange: (newUser: User) => {
          this.userInfo.name = newUser.name
          this.userInfo.age = newUser.age
        }
      })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .backgroundColor(Color.Pink)
  }
}

@Component
struct Child {
  @Prop
  user: User
  userChange: (newUser: User) => void = (newUser: User) => {
  }

  build() {
    Text('子组件:' + JSON.stringify(this.user))
      .padding(10)
      .backgroundColor('#0094ff')
      .fontColor(Color.White)
      .onClick(() => {
        this.userChange({
          name: '路飞',
          age: 26
        })
      })
  }
}

@Extend(Text)
function white() {
  .fontSize(20)
  .fontColor(Color.White)
}

9.@Link

使用@Link 可以实现父组件和子组件的双向同步,对于简单类型以及复杂类型的变量可以做到双向的数据同步,但是对于深层次的引用类型,它做不到。

9.1.简单类型

@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)
  }
}

说明:父组件和子组件的数据是同步的,做到了统一数据源。

9.2.复杂类型

interface IPerson {
  age: number
}

@Component
struct MyButtbon {
  @Link
  person: IPerson

  build() {
    Column() {
      Button(`子组件的数据 ${this.person.age}`)
        .onClick(() => {
          this.person.age++
        })
    }
  }
}


@Entry
@Component
struct Index {
  @State
  person: IPerson = {
    age: 100
  }

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

      Button(`父亲的数据 ${this.person.age}`)
        .onClick(() => {
          //对象里面可以直接用点++,在对象数组里面不可以点++,如this.starList[index].hot++
          this.person.age++
        })
      //   =====
      MyButtbon({ person: this.person })
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

说明:父组件和子组件的数据是同步的,做到了统一数据源。

9.3.深层次的引用类型

1.@Link对于深层次的引用类型数据无法修饰,以下的代码会报错

2.@Link对与ForEach循环出来的item也无法进行修饰

直接看例子:

import { promptAction } from '@kit.ArkUI'

interface IDog {
  weight: number
}

interface IPerson {
  // dog 引用数据类型 深层次 (引用数据中又嵌套了引用数据类型)
  dog: IDog
}


@Component
struct MyButtbon {
  //@Link     //无法运用于深层引用数据类型,这样会使子组件无法显示
  dog: IDog

  build() {
    Column() {
      Button(`子组件中的数据 ${this.dog.weight}`)

    }
  }
}


@Entry
@Component
struct Index {
  @State
  person: IPerson = {

    dog: {
      weight: 200
    }
  }

  build() {
    Column({ space: 10 }) {
      Button(`父组件中的数据 ${this.person.dog.weight}`)
        .onClick(() => {
          // 鸿蒙没有对深层次的数据 做监听 ------
          // UI 不会更新
          // 数据变化了,但是this.person.dog.weight++没有在UI上面渲染
          // this.person.dog.weight++ // 无效

          // 数组.splice
          // let item = this.list[index]  //通过item来获取到一个新的数组对象,在使用item.splice来获取想要的数据


          // this.list.age++       //对象数组类型这样写无法使age值在UI上面渲染
         //解决方法:
          const weight = this.person.dog.weight + 1
          this.person.dog = { weight: weight }

          // this.list[index] = xxxxx


          promptAction.showToast({ message: `${this.person.dog.weight}` })
        })
      //   无法使用@Link来双向同步,这里只能父传子
      MyButtbon({ dog: this.person.dog })

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

10.@Watch 监听器

如果需要关注某个状态变量的值是否改变,可以使用@Watch 为状态变量设置回调函数。

注意:

Watch无法单独使用,必须配合状态装饰器,比如@State、@Prop、@Link

语法:

@State
@Watch('方法名')
info:string=''

方法名(){
  // 数据改变之后会触发
}

举例:

@Entry
@Component
struct Index {
  @State num: number = 0
  
  用
  @Watch('getnum')
  @State list: number[] = []

  getnum() {

    //获取每个时间戳是否为偶数,如果为偶数就取下来放进新的数组在取它的长度
    this.num = this.list.filter(item => item % 2 == 0).length
  }

  build() {
    Column() {

      Text(`偶数的个数:${this.num}`)
      Text(`数组长度:${this.list.length}`)
        .onClick(() => {
          this.list.push(Date.now())

        })

    }.backgroundColor(Color.Pink)
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
  }
}

11.@Observed 和 @ObjectLink

之前都是修改嵌套对象的写法,会让页面刷新,降低用户的体验,为了解决这个问题,接下来@Observed@ObjectLink 这两个装饰器解决。

image.png

语法:

// @Observed 装饰类
@Observed
class ClassA{
  // 略
}

// 子组件
@Component 
struct Com{
  @ObjectLink
  数据:ClassA
}

注意:

1. @Observed只能修饰类,构造的数据必须要进行new的方式出现

2.@ObjectLink 只能用在子组件身上

3.这两个配合可以对深层次的属性进行修改,并且生效 比如类似这种深层次的属性: this.person.dog.weight++ // 可以生效

4.@objectLink能做到子组件修改父组件的深层次数据,但是修改的数据不能在UI上面进行改变,它跟其他的装饰器不同的是只有@objectLink能做到子组件修改父组件的深层次数据

12.@Provice和@Consume

将数据传递给后代,和后代的数据进行双向同步

语法:

// 写法 1:通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;

// 写法 2通过相同的变量别名绑定
@Provide b: number = 0;
@Consume('b') c: number;

举例:

interface IPrson {
  num: number
  add: () => void
}

@Component
struct Son {
  //儿子也需要用上Consume,用上Prop是undefined

  //num: number

  build() {
    Column() {
      Text(`儿子组件组件 `)

      SunZi()


    }.backgroundColor(Color.Pink)
    .width(300)
    .height(200)
    .border({
      width: 5,
      color: Color.Gray
    })
  }
}


@Component
struct SunZi {
  //num: number


  //探究二
  @Consume
  person: IPrson[]
  //这个变量名要跟爷爷组件传过来的变量名相同,而且不能给它进行赋值

  //探究一
  @Consume
  Function: IPrson

  build() {
    Column() {

      //探究一
      Text(`孙子组件  ${this.Function.num}`)
        .onClick(this.Function.add)


      //探究二
      Text(`${this.person[0].num}`)
        .onClick(() => {
          this.person[0].num++
        })

    }.backgroundColor(Color.Pink)
    .width(120)
    .height(100)
    .border({
      width: 5,
      color: Color.Gray
    })
  }
}

@Entry
@Component
struct Index {
  //1.num: number = 100      //对于简单类型可以直接双向绑定,并且UI生效

  //1.探究一
  @Provide
  person: IPrson[] = [{
    num: 0,
    add: () => {
    }
  }]

  //2.探究二:对于深层次引用类型的需要这样改数据
  @Provide
  Function: IPrson = {
    num: 0,
    add: () => {
      this.Function.num++
    }
  }

  build() {
    Column() {

      //探究一
      Text(`爷爷组件  ${this.Function.num}`)

        .onClick(this.Function.add) //——>括号里面相等于() => {
      // this.Function.num++
      // }
      /*.onClick(()=>{
        this.Function.add() //——>这一行代码相等于this.Function.num++
      })*/


      //探究二
      Text(`${this.person[0].num}`)
        .onClick(() => {
          this.person[0].num++
          AlertDialog.show({message:this.person[0].num.toString()})
        })

      Son()


    }.backgroundColor(Color.Pink)
    .width('100%')
    .height(300)
    .border({
      width: 5,
      color: Color.Gray
    })
  }
}

总结:

  • 子孙@Provide@Consume可以对简单类型,引用类型直接绑定,而且还可以引起UI生效。

写法1:

如:1.发送@Provide

image.png

2.接收@Consume

image.png

写法2:

如:1.发送@Provide image.png

2.接收@Consume

image.png

3.孙子组件里面修改数据(能修改数据,但是不能进行UI渲染改变)

image.png

  • @Provide@Consume也可以使用在爸爸与儿子的身份关系上,用法不变

总结:

只有@Observed@objectLink这一组可以对深层的引用类型,在二层组件或者三层组件进行对数据直接更改,同时还能进行统一数据源,但是不会引起UI改变。其他的修饰器都不可以。