5. Harmony 状态装饰器(组件内)

Posted by DH on November 27, 2024

状态修饰器

在鸿蒙上,状态装饰器用于修饰状态变量,当状态变量改变时,根据不同的状态装饰器进行相应的响应式UI刷新

各状态装饰器

@State

(1)被@State修饰的变量作用域是组件内部;

(2)其修饰的变量生命周期和其自定义组件的生命周期相同;

(3)必须进行初始化

(4)当装饰器修饰的是基本类型、对象时,数据的变化可以被装饰器发现。当修饰数组时,数组的增删可以被观察,但是数组内某个对象的属性被修改时,不会引起刷新

@Prop

父组件传值给子组件,如果父组件中的值修改时,子组件的变量还是用@State修饰,并不是引起UI的刷新。这时需要对子组件中的状态变量使用@Prop修饰。需要注意的是,被@Pro修饰的状态变量,在进入到后台时,即使值变化也无法进行页面的渲染。

子组件的修改不能同步到父组件!

@Entry
@Component
struct PropDemoPage{
  @State name :string= "张三";
  build() {
    Row(){
      childView({ childName:this.name} )

      Button("修改name")
        .onClick(()=>{
          this.name = "李四"
        })
    }
  }
}

@Component
struct childView{
  // 通过父组件传值
  @Prop childName:string = "";
  build() {
    Text(this.childName)
  }
}

子组件中被@Prop修饰的状态变量名字可以不用和父组件中被@State修饰的状态变量相同

当点击按钮修改name时,父组件中的name会变成李四,并同步到子组件展示:

@Prop(子组件) + @State(父组件) 只能实现父到子的同步刷新。如果想实现父子的双向同步更新,需要使用@link(子组件) + @State(父组件)。

实现一个效果:

  1. 点击“父组件传值给子组件”按钮,修改父组件中name的值,并传递到子组件,刷新子组件的UI:子组件从父组件获取的值:XX;

  2. 子组件输入框中,输入的文本能够同步给父组件,刷新父组件的UI:父组件输入框输入的值:XXXXX.

需要注意,输入框中childName,必须使用引用传递,而不是值传递,引用传递指向同一个地址,修改其中的值,才能够进行同步!!


@Entry
@Component
struct LinkDemoPage{
  @State name :string= "张三";
  build() {
    Column(){
      childView({ childName:this.name} )

      Button("父组件传值给子组件")
        .onClick(()=>{
          this.name = "李四"
        })

      Text("父组件输入框输入的值:" + this.name);
    }
  }
}

@Component
struct childView{
  // 被Link修饰的状态变量,不能被初始化
  @Link childName:string;
  build() {
    Column() {
      Text("子组件从父组件获取的值 : " + this.childName)
      TextInput({text:$$this.childName}).backgroundColor(Color.Pink)
    }.backgroundColor(Color.Gray)
  }
}

在输入框输入hello harmony ,父子组件均能刷新:

@Consumer 和 @Provider

我们用@Link就可以直线父子之间的同步。但是如果从父节点到子组件的路劲很长,例如父组件的状态变量值变化,需要同步到孙子的孙子的孙子组件,就需要一层层的传递,较为麻烦。这种场景就可以使用@Provider和@Consumer。

其中:

@Provide 用于修饰父组件中的状态变量;@Consumer用于修饰子组件中的状态变量。并且被这一对修饰的状态变量是双向同步的,变量的名字必须一致(除非只用别名)

例子如下:


@Entry
@Component
struct ProviderDemoPage{
  @Provide name :string= "张三";
  build() {
    Column(){
      childView()

      Button("父组件传值给子组件")
        .onClick(()=>{
          this.name = "李四"
        })

      Text("父组件输入框输入的值:" + this.name);
    }
  }
}

@Component
struct childView{
  // 被Consumer修饰的状态变量,不能被初始化
  @Consume name:string;
  build() {
    Column() {
      Text("子组件1从父组件获取的值 : " + this.name)
      Text("子组件2从父组件获取的值 : " + this.name)
      TextInput({text:$$this.name})
        .backgroundColor(Color.Pink)
    }.backgroundColor(Color.Gray)
  }
}

初始化渲染时,子组件从父组件拿到name的值是”张三”,并且渲染到页面。当点击父组件的按钮,name的值变为”李四”,会同步刷线子组件中所有值。当子组件的输入框输入时,又会将输入框的值同步给父组件

@Provider 和 @Consumer是生产者和消费者模式.

无论是@State和@Prop配对的父到子单向同步,还是@State和@Link的父子双向同步,或者是@Provider和@Consumer的父子、子孙之间的双向同步,都存在一个问题,同步的数据只能是顶层对象,如果顶层对象的属性也是对象,那么非顶层对象的变化,是不会在父子之间进行同步的。例如,一个学生数字,包含了一个学生对象,如果这个学生对象的名字改变了,通过以上的修饰符是不能进行同步的,也就不能解决嵌套对象刷新的问题。此时就需要使用@Observer 和 @ObjectLink进行修饰

其中:

@Observer:修饰最终变化的类,例如上述例子中应该修饰学生累;@ObjectLink用在子组件中,修饰被观察的类的对象,上述例子中就应该是修饰学生的对象。

@Entry
@Component
struct ProviderDemoPage{
  @State students :[Student]= [new Student("张三",10)];
  build() {
    Column(){
      childView({student: this.students[0]})

      Button("父组件传值给子组件")
        .onClick(()=>{
           let student:Student = this.students[0];
           student.name = "王五";
        })
    }
  }
}

@Component
struct childView{
  // 被Consumer修饰的状态变量,不能被初始化
  @ObjectLink student:Student;
  build() {
    Column() {
      Text("子组件从父组件获取的值 : " + this.student.name);
      TextInput({text:$$this.student.name})
        .backgroundColor(Color.Pink)
    }.backgroundColor(Color.Gray)
  }

}

@Observed
class Student{
  constructor(name: string, age: number) {
    this.name = name;
    this._age = age;
  }

  private _name: string = "张三";

  public set name(value: string) {
    this._name = value;
  }

  public get name(): string {
    return this._name;
  }

  private _age: number = 0;

  public set age(value: number) {
    this._age = value;
  }

  public get age(): number {
    return this._age;
  }
}