注解调用一句话总结作用域
@State双向数据传递,但是多层对象更新不生效,需要替换整个对象定义变量
@Prop父传子的单向数据传递,子组件的更新不影响父组件定义变量
@Link父传子的双向数据传递,子组件的更新也会影响父组件定义变量
@Observed & @ObjectLink嵌套对象或数组的场景中进行双向数据同步定义类
@Provide & @Consume父传隔多层的子组件的数据双向同步定义变量
@Watch监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用定义变量
$$语法由@State、@Link和@Prop装饰的变量绑定到系统内置组件变量定义变量
@Track@Track装饰的属性变化时,只会触发该属性关联的UI更新定义变量
@ObservedV2 & @Trace对嵌套类对象属性变化时的双向数据传递定义变量,嵌套对象
@Monitor监听状态变量的变化,当状态变量变化时,@Monitor的回调方法将被调用(只对V2有效)定义变量

@State

State的更新只依赖于第一层对象的嵌套,多层对象嵌套下则更新不生效,可使用console.log(Object.keys(object));的方式看看当前可响应式更新的key值的是有哪些

在这里插入图片描述

如果是需要更新嵌套的对象的话,只能更新第一层参数,对象要整个进行替换

在这里插入图片描述

@Prop

父传子单向更新传递,只能相隔一层之间的关系

注意
  • Prop的变量不能赋值

在这里插入图片描述

父组件:字段的数据说明还是用@State来定义

在这里插入图片描述

子组件:字段用@Prop定义,子组件对值的修改只能影响自己的更新,不会传递到父组件

在这里插入图片描述

@Link

父子双向更新传递,对象和基本数据类型都可以,也是定义在变量上,用法同@Prop,不再赘述

注意
  • Link的变量不能赋值

在这里插入图片描述


提问

既然@State和@Link都可以双向传递,那@State和@Link有什么区别?

  • 页面1

在这里插入图片描述

  • 页面2

在这里插入图片描述

最后结果[点击前和点击后对比]

点击前

点击后

答案:
  • @State装饰的变量是组件内部的状态数据,当状态数据被修改时,会调用当前组件的build()进行UI刷新
  • @Link装饰的变量可以和父组件的@State变量进行双向的数据绑定,对子组件和父组件都可以调用组件的build()进行UI刷新

@Observed & @ObjectLink

背景
  • 上面的装饰器仅能观察到第一层数据的变化
  • 对于多层嵌套的情况,比如对象数组等,他们的第二层的属性变化是无法观察到的
  • 这就引出了@Observed/@ObjectLink装饰器
作用域
  • 用于在涉及嵌套对象或数组的场景中进行双向数据同步
注意
  • ObjectLink修饰符不能用在 Entry 修饰的组件中

对对象类修饰

在这里插入图片描述

父组件:对使用的类要加上@State定义,并将数组对象传递过去

在这里插入图片描述

子组件:对对象进行数据操作,均会同步到父组件

在这里插入图片描述

@Provide & @Consume

当组件存在多层组件的时候,需要在底下多层的组件中获取父组件的数据时用到

注意
  • Provide和Consume修饰的变量名字需一致
  • Consume的变量不能赋值

在这里插入图片描述

父组件修饰

在这里插入图片描述

子子子子组件使用

在这里插入图片描述

@Watch

当组件的变量变化需要监听使用的时候,可以用到@Watch

注意
  • @Watch必须配合@State一起使用
  • 在第一次初始化的时候,@Watch装饰的方法不会被调用。只有在后续状态改变时,才会调用@Watch回调方法
  • 避免无限循环,不要在@Watch的回调方法里修改当前装饰的状态变量
  • @Watch参数为必选,且参数类型必须是string,否则编译期会报错
//正确写法
@State @Watch('change') num: number = 10;
@State total: number = 5;

//正确写法
change() {
  //当数量增加时,在这里可以做其他变量的逻辑
  console.log(`xxx=`+(this.total + this.num));
}

//错误写法
onChange() {
  console.log(`xxx`);
}

build() {
    Column() {
      Button('增加数量')
        .onClick(() => {
          this.num++
        })
    }
  }

$$语法

主要是用于内置组件双向同步,比如Checkbox的select属性,TextInput的text属性等等,以TextInput方法的text参数为例

  • 错误写法
@Entry
@Component
struct TextInputExample {
  @State text: string = ''
  controller: TextInputController = new TextInputController()

  build() {
    Column({ space: 20 }) {
      Text(this.text)
      TextInput({ text: this.text, placeholder: 'input your word...', controller: this.controller })
        .width(300)
        .onChange((value: string) => {
            this.text = value
        })
    }.width('100%').height('100%')
  }
}
  • 正确写法
@Entry
@Component
struct TextInputExample {
  @State text: string = ''
  controller: TextInputController = new TextInputController()

  build() {
    Column({ space: 20 }) {
      Text(this.text)
      TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
        .width(300)
    }.width('100%').height('100%')
  }
}

@Track

@Track装饰器装饰的是class类的字段,被@Track装饰的字段可以在UI中使用,非@Track装饰的属性不能在UI中使用,主要是用作优化UI的冗余刷新

注意
  • @Track没有深度观测的功能,用于基础数据类型的传递,对象则不行
用法
  • 错误写法&正确用法
class Person {
  // id被@Track装饰
  @Track id: number;
  // age未被@Track装饰
  age: number;
}

@Entry
@Component
struct Parent {
  
  ......
  
  build() {
    // 错误写法
    // 没有被@Track装饰的属性不可以在UI中使用,运行时会报错
    Text(`Parent id is: ${this.parent.id} and Parent age is: ${this.parent.age}`)
    // 正确写法
    Text(`Parent id is: ${this.parent.id}`)
        .onClick(()=>{
            //没有被@Track装饰的属性可以在其他地方使用
            console.log(`Parent age is: ${this.parent.age}`);
        })
  }
}
  • @Track原理
//带@Track 
class LogTrack {
  @Track str1: string;
  @Track str2: string;
}

//不带@Track 
class LogNotTrack {
  str1: string;
  str2: string;
}

@Entry
@Component
struct AddLog {
  @State logTrack: LogTrack = new LogTrack('Hello');
  @State logNotTrack: LogNotTrack = new LogNotTrack('你好');
 
  isRender(index: number) {
    console.log(`Text ${index} is rendered`);
    return 50;
  }
  
  build() {
    Row() {
      Column() {
        Text(this.logTrack.str1) // 带@TrackText1
            .fontSize(this.isRender(1))
        Text(this.logTrack.str2) // 带@TrackText2
            .fontSize(this.isRender(2))
        Button('change logTrack.str1')
          .onClick(() => {
            this.logTrack.str1 = 'Bye'; //按钮1
          })
        Text(this.logNotTrack.str1) // 不带@TrackText3
            .fontSize(this.isRender(3))
        Text(this.logNotTrack.str2) // 不带@TrackText4
            .fontSize(this.isRender(4))
        Button('change logNotTrack.str1')
          .onClick(() => {
            this.logNotTrack.str1 = '再见'; //按钮2
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

分别点击按钮1和按钮2,可以看到日志的输出,在带@Track装饰器装饰,只有一条日志输出,避免了冗余刷新,未被@Track装饰器装饰,有两条日志输出,存在冗余刷新

//按钮1的日志
Text 1 is rendered
//按钮2的日志
Text 3 is rendered
Text 4 is rendered

@ObservedV2和@Trace

状态管理V1版本对嵌套类对象属性变化存在局限性,于是创建状态管理V2版本来解决嵌套对象的数据同步问题

  • V1版本

点击Text组件增加age的值时,不会触发UI刷新。因为在现有的状态管理框架下,无法观测到嵌套类中属性age的值变化

@Observed
class Father {
  son: Son;
}
@Observed
class Son {
  name: string;
  age: number;
}
@Entry
@Component
struct Index {
  @State father: Father = new Father("John", 8);

  build() {
    Row() {
      Column() {
        Text(`name: ${this.father.son.name} age: ${this.father.son.age}`)
          .onClick(() => {
            this.father.son.age++;
          })
      }
    }
  }
}
  • V1版本解决方案

    • 通过@ObjectLink与自定义组件的方式来解决深度嵌套对象的数据同步问题
    • 通过接口回调的方式来解决数据嵌套更新的问题
    • 通过@ObservedV2和@Trace方式来解决数据嵌套更新的问题
  • 方式一:@ObjectLink与自定义组件

@Observed
class Father {
  son: Son;
}
@Observed
class Son {
  name: string;
  age: number;
}
@Component
struct Child {
  @ObjectLink son: Son;

  build() {
    Row() {
      Column() {
        Text(`name: ${this.son.name} age: ${this.son.age}`)
          .onClick(() => {
            this.son.age++;
          })
      }
    }
  }
}
@Entry
@Component
struct Index {
  @State father: Father = new Father("John", 8);

  build() {
    Column() {
      Child({son: this.father.son})
    }
  }
}
  • 方式二:通过接口回调
@Observed
class Father {
  son: Son;
}
@Observed
class Son {
  name: string;
  age: number;
}
@Entry
@Component
struct Index {

  private sonListeners = new ArrayList<OnSonChangedListener>()
  @State father: Father = new Father("John", 8);

  registerSonChanged(listener: OnSonChangedListener) {
    if (!this.sonListeners.has(listener)) {
      this.sonListeners.add(listener)
    }
  }

  build() {
    Row() {
      Column() {
        Text(`name: ${this.father.son.name} age: ${this.father.son.age}`)
          .onClick(() => {
            this.sonListeners.forEach(listener => {
              listener.onSonChanged()
            })
          })
      }
    }
  }
}

export interface OnSonChangedListener {
  onSonChanged: () => void
}
  • 方式三:@ObservedV2和@Trace
注意
  • 非@Trace装饰的成员属性用在UI上无法触发UI刷新
  • @Trace不能用在没有被@ObservedV2装饰的class上
  • @ObservedV2、@Trace不能与@Observed、@Track混合使用
  • @ObservedV2、@Trace要使用在@ComponentV2组件中
@ObservedV2
class Son {
  @Trace age: number = 100;
}
class Father {
  son: Son = new Son();
}
@Entry
@ComponentV2
struct Index {
  father: Father = new Father();

  build() {
    Column() {
      // 当点击改变age时,Text组件会刷新
      Text(`${this.father.son.age}`)
        .onClick(() => {
          this.father.son.age++;
        })
    }
  }
}

@Monitor

@Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力

注意
  • @Monitor装饰器支持在@ComponentV2装饰的自定义组件中使用
  • 未被@Trace装饰的属性无法被@Monitor监听到变化
  • 相比V1版本,增加可以获取变化之前的值的能力
@ObservedV2
class Info {
  @Trace name: string = "Tom";
  @Trace region: string = "North";
  @Trace job: string = "Teacher";
  age: number = 25;
  
  @Monitor("name")
  onNameChange(monitor: IMonitor) {
    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }
  @Monitor("age")
  onAgeChange(monitor: IMonitor) {
    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }
  @Monitor("region", "job")
  onChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      console.log(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
    })
  }
}
@Entry
@ComponentV2
struct Index {
  info: Info = new Info();
  build() {
    Column() {
      Button("change name")
        .onClick(() => {
          this.info.name = "Jack"; // 能够触发onNameChange方法
        })
      Button("change age")
        .onClick(() => {
          this.info.age = 26; // 不能够触发onAgeChange方法
        })
      Button("change region")
        .onClick(() => {
          this.info.region = "South"; // 能够触发onChange方法
        })
      Button("change job")
        .onClick(() => {
          this.info.job = "Driver"; // 能够触发onChange方法
        })
    }
  }
}
高阶用法

① 可以监听数组具体某个Index下的值,但是不能监听(Array、Map、Date、Set)

@ObservedV2
class ArrMonitor {
  @Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]];
  @Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]];
  @Trace infoArr: Info[] = [new Info("Jack", 24), new Info("Lucy", 18)];
  
  //监听下标为[0][0]和[1][1]的变化
  @Monitor("dimensionTwo.0.0", "dimensionTwo.1.1")
  onDimensionTwoChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      console.log(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
    })
  }
  
  //监听下标为[0][0][0]和[1][1][0]的变化
  @Monitor("dimensionThree.0.0.0", "dimensionThree.1.1.0")
  onDimensionThreeChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      console.log(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
    })
  }
  
  //监听下标为[0]和[1]Info类中属性name、age
  @Monitor("infoArr.0.name", "infoArr.1.age")
  onInfoArrPropertyChange(monitor: IMonitor) {
    monitor.dirty.forEach((path: string) => {
      console.log(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
    })
  }
  
  //监听到infoArr整体赋值的变化
  @Monitor("infoArr")
  onInfoArrChange(monitor: IMonitor) {
    console.log(`infoArr whole change`);
  }
  
  //监听到infoArr的长度变化
  @Monitor("infoArr.length")
  onInfoArrLengthChange(monitor: IMonitor) {
    console.log(`infoArr length change`);
  }
}

② 监听对象值未变化时不会回调

@ObservedV2
class Info {
  @Trace person: Person;
  @Monitor("person.name")
  onNameChange(monitor: IMonitor) {
    console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }
  @Monitor("person.age")
  onAgeChange(monitor: IMonitor) {
    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  }
}

@Entry
@ComponentV2
struct Index {
  info: Info = new Info("Tom", 25);
  build() {
    Column() {
      Button("Step1、Only change name")
        .onClick(() => {
          this.info.person = new Person("Jack", 25);  // 名字不同,能够触发onNameChange方法,岁数相同,不触发onAgeChange方法
        })
      Button("Step2、Only change age")
        .onClick(() => {
          this.info.person = new Person("Jack", 18);  // 岁数不同,能够触发onAgeChange方法,名字相同,不触发onNameChange方法
        })
      Button("Step3、Change name and age")
        .onClick(() => {
          this.info.person = new Person("Lucy", 19);  // 能够触发onNameChange、onAgeChange方法
        })
    }
  }
}
@Monitor与@Watch对比
对比@Watch@Monitor
参数回调方法名监听状态变量名、属性名
监听目标数只能监听单个状态变量能同时监听多个状态变量
监听能力跟随状态变量观察能力(一层)跟随状态变量观察能力(深层)
能否获取变化前的值不能获取变化前的值能获取变化前的值
监听条件监听对象为状态变量监听对象为状态变量或为@Trace装饰的类成员属性
使用限制仅能在@Component装饰的自定义组件中使用能在@ComponentV2装饰的自定义组件中使用,也能在@ObservedV2装饰的类中使用
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐