鸿蒙HarmonyOS 5.0开辟:@Monitor装饰器:状态变量修改监听 ...

打印 上一主题 下一主题

主题 982|帖子 982|积分 2946

 往期鸿蒙全套实战文章必看:(文中附带鸿蒙全栈学习资料)



  • 鸿蒙开辟核心知识点,看这篇文章就够了
  • 最新版!鸿蒙HarmonyOS Next应用开辟实战学习路线
  • 鸿蒙HarmonyOS NEXT开辟技能最全学习路线指南
  • 鸿蒙应用开辟实战项目,看这一篇文章就够了(部门项目附源码)

@Monitor装饰器:状态变量修改监听

为了增强状态管理框架对状态变量变化的监听能力,开辟者可以使用@Monitor装饰器对状态变量进行监听。
@Monitor提供了对V2状态变量的监听。
   说明
  @Monitor装饰器从API version 12开始支持。
  概述

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


  • @Monitor装饰器支持在@ComponentV2装饰的自界说组件中使用,未被状态变量装饰器@Local、@Param、@Provider、@Consumer、@Computed装饰的变量无法被@Monitor监听到变化。
  • @Monitor装饰器支持在类中与@ObservedV2、@Trace共同使用,不允许在未被@ObservedV2装饰的类中使用@Monitor装饰器。未被@Trace装饰的属性无法被@Monitor监听到变化。
  • 当观测的属性变化时,@Monitor装饰器界说的回调方法将被调用。判断属性是否变化使用的是严格相等(===),当严格相等为false的情况下,就会触发@Monitor的回调。当在一次事故中多次改变同一个属性时,将会使用初始值和最终值进行比较以判断是否变化。
  • 单个@Monitor装饰器能够同时监听多个属性的变化,当这些属性在一次事故中共同变化时,只会触发一次@Monitor的回调方法。
  • @Monitor装饰器具有深度监听的能力,能够监听嵌套类、多维数组、对象数组中指定项的变化。对于嵌套类、对象数组中成员属性变化的监听要求该类被@ObservedV2装饰且该属性被@Trace装饰。
  • 在继承类场景中,可以在父子组件中对同一个属性分别界说@Monitor进行监听,当属性变化时,父子组件中界说的@Monitor回调均会被调用。
  • 和@Watch装饰器类似,开辟者需要本身界说回调函数,区别在于@Watch装饰器将函数名作为参数,而@Monitor直接装饰回调函数。@Monitor与@Watch的对比可以查看@Monitor与@Watch的对比。
状态管理V1版本@Watch装饰器的局限性

现有状态管理V1版本无法实现对对象、数组中某一单个属性或数组项变化的监听,且无法获取变化之前的值。
  1. @Observed
  2. class Info {
  3.   name: string = "Tom";
  4.   age: number = 25;
  5. }
  6. @Entry
  7. @Component
  8. struct Index {
  9.   @State @Watch('onInfoChange') info: Info = new Info();
  10.   @State @Watch('onNumArrChange') numArr: number[] = [1,2,3,4,5];
  11.   onInfoChange() {
  12.     console.log(`info after change name: ${this.info.name}, age: ${this.info.age} `);
  13.   }
  14.   onNumArrChange() {
  15.     console.log(`numArr after change ${JSON.stringify(this.numArr)}`);
  16.   }
  17.   build() {
  18.     Row() {
  19.       Column() {
  20.         Button("change info name")
  21.           .onClick(() => {
  22.             this.info.name = "Jack";
  23.           })
  24.         Button("change info age")
  25.           .onClick(() => {
  26.             this.info.age = 30;
  27.           })
  28.         Button("change numArr[2]")
  29.           .onClick(() => {
  30.             this.numArr[2] = 5;
  31.           })
  32.         Button("change numArr[3]")
  33.           .onClick(() => {
  34.             this.numArr[3] = 6;
  35.           })
  36.       }
  37.       .width('100%')
  38.     }
  39.     .height('100%')
  40.   }
  41. }
复制代码
上述代码中,点击"change info name"更改info中的name属性或点击"change info age"更改age时,均会触发info注册的@Watch回调。点击"change numArr[2]"更改numArr中的第3个元素或点击"change numArr[3]"更改第4个元素时,均会触发numArr注册的@Watch回调。在这两个回调中,由于无法获取数据更改前的值,在业务逻辑更加复杂的场景下,无法精确知道是哪一个属性或元素发生了改变从而触发了@Watch事故,这不便于开辟者对变量的更改进行精确监听。因此推出@Monitor装饰器实现对对象、数组中某一单个属性或数组项变化的监听,并且能够获取到变化之前的值。
装饰器说明

@Monitor属性装饰器说明装饰器参数字符串范例的对象属性名。可同时监听多个对象属性,每个属性以逗号隔开,比方@Monitor("prop1", "prop2")。可监听深层的属性变化,如多维数组中的某一个元素,嵌套对象或对象数组中的某一个属性。装饰对象@Monitor装饰成员方法。当监听的属性发生变化时,会触发该回调方法。该回调方法以IMonitor范例的变量作为参数,开辟者可以从该参数中获取变化前后的相关信息。 接口说明

IMonitor范例

IMonitor范例的变量用作@Monitor装饰方法的参数。
属性范例参数返回值说明dirtyArray<string>无无保存发生变化的属性名。value<T>functionpath?: stringIMonitorValue<T>获得指定属性(path)的变化信息。当不填path时返回@Monitor监听顺序中第一个改变的属性的变化信息。 IMonitorValue<T>范例

IMonitorValue<T>范例保存了属性变化的信息,包括属性名、变化前值、当前值。
属性范例说明beforeT监听属性变化之前的值。nowT监听属性变化之后的当前值。pathstring监听的属性名。 监听变化

在@ComponentV2装饰的自界说组件中使用@Monitor

使用@Monitor监听的状态变量发生变化时,会触发@Monitor的回调方法。


  • @Monitor监听的变量需要被@Local、@Param、@Provider、@Consumer、@Computed装饰,未被状态变量装饰器装饰的变量在变化时无法被监听。@Monitor可以同时监听多个状态变量,这些变量名之间用","隔开。
    1. @Entry
    2. @ComponentV2
    3. struct Index {
    4.   @Local message: string = "Hello World";
    5.   @Local name: string = "Tom";
    6.   @Local age: number = 24;
    7.   @Monitor("message", "name")
    8.   onStrChange(monitor: IMonitor) {
    9.     monitor.dirty.forEach((path: string) => {
    10.       console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
    11.     })
    12.   }
    13.   build() {
    14.     Column() {
    15.       Button("change string")
    16.         .onClick(() => {
    17.           this.message += "!";
    18.           this.name = "Jack";
    19.       })
    20.     }
    21.   }
    22. }
    复制代码
  • @Monitor监听的状态变量为类对象时,仅能监听对象整体的变化。监听类属性的变化需要类属性被@Trace装饰。
    1. class Info {
    2.   name: string;
    3.   age: number;
    4.   constructor(name: string, age: number) {
    5.     this.name = name;
    6.     this.age = age;
    7.   }
    8. }
    9. @Entry
    10. @ComponentV2
    11. struct Index {
    12.   @Local info: Info = new Info("Tom", 25);
    13.   @Monitor("info")
    14.   infoChange(monitor: IMonitor) {
    15.     console.log(`info change`);
    16.   }
    17.   @Monitor("info.name")
    18.   infoPropertyChange(monitor: IMonitor) {
    19.     console.log(`info name change`);
    20.   }
    21.   build() {
    22.     Column() {
    23.       Text(`name: ${this.info.name}, age: ${this.info.age}`)
    24.       Button("change info")
    25.         .onClick(() => {
    26.           this.info = new Info("Lucy", 18); // 能够监听到
    27.         })
    28.       Button("change info.name")
    29.         .onClick(() => {
    30.           this.info.name = "Jack"; // 监听不到
    31.         })
    32.     }
    33.   }
    34. }
    复制代码
在@ObservedV2装饰的类中使用@Monitor

使用@Monitor监听的属性发生变化时,会触发@Monitor的回调方法。


  • @Monitor监听的对象属性需要被@Trace装饰,未被@Trace装饰的属性的变化无法被监听。@Monitor可以同时监听多个属性,这些属性之间用","隔开。
  1. @ObservedV2
  2. class Info {
  3.   @Trace name: string = "Tom";
  4.   @Trace region: string = "North";
  5.   @Trace job: string = "Teacher";
  6.   age: number = 25;
  7.   // name被@Trace装饰,能够监听变化
  8.   @Monitor("name")
  9.   onNameChange(monitor: IMonitor) {
  10.     console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  11.   }
  12.   // age未被@Trace装饰,不能监听变化
  13.   @Monitor("age")
  14.   onAgeChange(monitor: IMonitor) {
  15.     console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  16.   }
  17.   // region与job均被@Trace装饰,能够监听变化
  18.   @Monitor("region", "job")
  19.   onChange(monitor: IMonitor) {
  20.     monitor.dirty.forEach((path: string) => {
  21.       console.log(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
  22.     })
  23.   }
  24. }
  25. @Entry
  26. @ComponentV2
  27. struct Index {
  28.   info: Info = new Info();
  29.   build() {
  30.     Column() {
  31.       Button("change name")
  32.         .onClick(() => {
  33.           this.info.name = "Jack"; // 能够触发onNameChange方法
  34.         })
  35.       Button("change age")
  36.         .onClick(() => {
  37.           this.info.age = 26; // 不能够触发onAgeChange方法
  38.         })
  39.       Button("change region")
  40.         .onClick(() => {
  41.           this.info.region = "South"; // 能够触发onChange方法
  42.         })
  43.       Button("change job")
  44.         .onClick(() => {
  45.           this.info.job = "Driver"; // 能够触发onChange方法
  46.         })
  47.     }
  48.   }
  49. }
复制代码


  • @Monitor可以监听深层属性的变化,该深层属性需要被@Trace装饰。
  1. @ObservedV2
  2. class Inner {
  3.   @Trace num: number = 0;
  4. }
  5. @ObservedV2
  6. class Outer {
  7.   inner: Inner = new Inner();
  8.   @Monitor("inner.num")
  9.   onChange(monitor: IMonitor) {
  10.     console.log(`inner.num change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  11.   }
  12. }
  13. @Entry
  14. @ComponentV2
  15. struct Index {
  16.   outer: Outer = new Outer();
  17.   build() {
  18.     Column() {
  19.       Button("change name")
  20.         .onClick(() => {
  21.           this.outer.inner.num = 100; // 能够触发onChange方法
  22.         })
  23.     }
  24.   }
  25. }
复制代码


  • 在继承类场景下,可以在继承链中对同一个属性进行多次监听。
  1. @ObservedV2
  2. class Base {
  3.   @Trace name: string;
  4.   // 基类监听name属性
  5.   @Monitor("name")
  6.   onBaseNameChange(monitor: IMonitor) {
  7.     console.log(`Base Class name change`);
  8.   }
  9.   constructor(name: string) {
  10.     this.name = name;
  11.   }
  12. }
  13. @ObservedV2
  14. class Derived extends Base {
  15.   // 继承类监听name属性
  16.   @Monitor("name")
  17.   onDerivedNameChange(monitor: IMonitor) {
  18.     console.log(`Derived Class name change`);
  19.   }
  20.   constructor(name: string) {
  21.     super(name);
  22.   }
  23. }
  24. @Entry
  25. @ComponentV2
  26. struct Index {
  27.   derived: Derived = new Derived("AAA");
  28.   build() {
  29.     Column() {
  30.       Button("change name")
  31.         .onClick(() => {
  32.           this.derived.name = "BBB"; // 能够先后触发onBaseNameChange、onDerivedNameChange方法
  33.         })
  34.     }
  35.   }
  36. }
复制代码
通用监听能力

@Monitor还有一些通用的监听能力。


  • @Monitor支持对数组中的项进行监听,包括多维数组,对象数组。@Monitor无法监听内置范例(Array、Map、Date、Set)的API调用引起的变化。当@Monitor监听数组整体时,只能观测到数组整体的赋值。可以通过监听数组的长度变化来判断数组是否有插入、删除等变化。当前仅支持使用"."的方式表达深层属性、数组项的监听。
  1. @ObservedV2
  2. class Info {
  3.   @Trace name: string;
  4.   @Trace age: number;
  5.   
  6.   constructor(name: string, age: number) {
  7.     this.name = name;
  8.     this.age = age;
  9.   }
  10. }
  11. @ObservedV2
  12. class ArrMonitor {
  13.   @Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]];
  14.   @Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]];
  15.   @Trace infoArr: Info[] = [new Info("Jack", 24), new Info("Lucy", 18)];
  16.   // dimensionTwo为二维简单类型数组,且被@Trace装饰,能够观测里面的元素变化
  17.   @Monitor("dimensionTwo.0.0", "dimensionTwo.1.1")
  18.   onDimensionTwoChange(monitor: IMonitor) {
  19.     monitor.dirty.forEach((path: string) => {
  20.       console.log(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
  21.     })
  22.   }
  23.   // dimensionThree为三维简单类型数组,且被@Trace装饰,能够观测里面的元素变化
  24.   @Monitor("dimensionThree.0.0.0", "dimensionThree.1.1.0")
  25.   onDimensionThreeChange(monitor: IMonitor) {
  26.     monitor.dirty.forEach((path: string) => {
  27.       console.log(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
  28.     })
  29.   }
  30.   // Info类中属性name、age均被@Trace装饰,能够监听到变化
  31.   @Monitor("infoArr.0.name", "infoArr.1.age")
  32.   onInfoArrPropertyChange(monitor: IMonitor) {
  33.     monitor.dirty.forEach((path: string) => {
  34.       console.log(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
  35.     })
  36.   }
  37.   // infoArr被@Trace装饰,能够监听到infoArr整体赋值的变化
  38.   @Monitor("infoArr")
  39.   onInfoArrChange(monitor: IMonitor) {
  40.     console.log(`infoArr whole change`);
  41.   }
  42.   // 能够监听到infoArr的长度变化
  43.   @Monitor("infoArr.length")
  44.   onInfoArrLengthChange(monitor: IMonitor) {
  45.     console.log(`infoArr length change`);
  46.   }
  47. }
  48. @Entry
  49. @ComponentV2
  50. struct Index {
  51.   arrMonitor: ArrMonitor = new ArrMonitor();
  52.   build() {
  53.     Column() {
  54.       Button("Change dimensionTwo")
  55.         .onClick(() => {
  56.           // 能够触发onDimensionTwoChange方法  
  57.           this.arrMonitor.dimensionTwo[0][0]++;
  58.           this.arrMonitor.dimensionTwo[1][1]++;
  59.         })
  60.       Button("Change dimensionThree")
  61.         .onClick(() => {
  62.           // 能够触发onDimensionThreeChange方法
  63.           this.arrMonitor.dimensionThree[0][0][0]++;
  64.           this.arrMonitor.dimensionThree[1][1][0]++;
  65.         })
  66.       Button("Change info property")
  67.         .onClick(() => {
  68.           // 能够触发onInfoArrPropertyChange方法
  69.           this.arrMonitor.infoArr[0].name = "Tom";
  70.           this.arrMonitor.infoArr[1].age = 19;
  71.         })
  72.       Button("Change whole infoArr")
  73.         .onClick(() => {
  74.           // 能够触发onInfoArrChange、onInfoArrPropertyChange、onInfoArrLengthChange方法
  75.           this.arrMonitor.infoArr = [new Info("Cindy", 8)];
  76.         })
  77.       Button("Push new info to infoArr")
  78.         .onClick(() => {
  79.           // 能够触发onInfoArrPropertyChange、onInfoArrLengthChange方法
  80.           this.arrMonitor.infoArr.push(new Info("David", 50));
  81.         })
  82.     }
  83.   }
  84. }
复制代码


  • 对象整体改变,但监听的属性稳定时,不触发@Monitor回调。
下面的示例按照Step1-Step2-Step3的顺序点击,体现为代码注释中的活动。
假如只点击Step2或Step3,改变name、age的值,此时会触发onNameChange和onAgeChange方法。
  1. @ObservedV2
  2. class Info {
  3.   @Trace person: Person;
  4.   @Monitor("person.name")
  5.   onNameChange(monitor: IMonitor) {
  6.     console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  7.   }
  8.   @Monitor("person.age")
  9.   onAgeChange(monitor: IMonitor) {
  10.     console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  11.   }
  12.   constructor(name: string, age: number) {
  13.     this.person = new Person(name, age);
  14.   }
  15. }
  16. @ObservedV2
  17. class Person {
  18.   @Trace name: string;
  19.   @Trace age: number;
  20.   constructor(name: string, age: number) {
  21.     this.name = name;
  22.     this.age = age;
  23.   }
  24. }
  25. @Entry
  26. @ComponentV2
  27. struct Index {
  28.   info: Info = new Info("Tom", 25);
  29.   build() {
  30.     Column() {
  31.       Button("Step1、Only change name")
  32.         .onClick(() => {
  33.           this.info.person = new Person("Jack", 25);  // 能够触发onNameChange方法,不触发onAgeChange方法
  34.         })
  35.       Button("Step2、Only change age")
  36.         .onClick(() => {
  37.           this.info.person = new Person("Jack", 18);  // 能够触发onAgeChange方法,不触发onNameChange方法
  38.         })
  39.       Button("Step3、Change name and age")
  40.         .onClick(() => {
  41.           this.info.person = new Person("Lucy", 19);  // 能够触发onNameChange、onAgeChange方法
  42.         })
  43.     }
  44.   }
  45. }
复制代码


  • 在一次事故中多次改变被@Monitor监听的属性,以最后一次修改为准。
  1. @ObservedV2
  2. class Frequence {
  3.   @Trace count: number = 0;
  4.   @Monitor("count")
  5.   onCountChange(monitor: IMonitor) {
  6.     console.log(`count change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  7.   }
  8. }
  9. @Entry
  10. @ComponentV2
  11. struct Index {
  12.   frequence: Frequence = new Frequence();
  13.   build() {
  14.     Column() {
  15.       Button("change count to 1000")
  16.         .onClick(() => {
  17.           for (let i = 1; i <= 1000; i++) {
  18.             this.frequence.count = i;
  19.           }
  20.         })
  21.       Button("change count to 0 then to 1000")
  22.         .onClick(() => {
  23.           for (let i = 999; i >= 0; i--) {
  24.             this.frequence.count = i;
  25.           }
  26.           this.frequence.count = 1000; // 最终不触发onCountChange方法
  27.         })
  28.     }
  29.   }
  30. }
复制代码
在点击按钮"change count to 1000"后,会触发一次onCountChange方法,并输出日志"count change from 0 to 1000"。在点击按钮"change count to 0 then to 1000"后,由于事故前后属性count的值并没有改变,都为1000,所以不触发onCountChange方法。
限定条件

使用@Monitor需要注意如下限定条件:


  • 不建议在一个类中对同一个属性进行多次@Monitor的监听。当一个类中存在对一个属性的多次监听时,只有最后一个界说的监听方法会收效。
  1. @ObservedV2
  2. class Info {
  3.   @Trace name: string = "Tom";
  4.   @Monitor("name")
  5.   onNameChange(monitor: IMonitor) {
  6.     console.log(`onNameChange`);
  7.   }
  8.   @Monitor("name")
  9.   onNameChangeDuplicate(monitor: IMonitor) {
  10.     console.log(`onNameChangeDuplicate`);
  11.   }
  12. }
  13. @Entry
  14. @ComponentV2
  15. struct Index {
  16.   info: Info = new Info();
  17.   build() {
  18.     Column() {
  19.       Button("change name")
  20.         .onClick(() => {
  21.           this.info.name = "Jack"; // 仅会触发onNameChangeDuplicate方法
  22.         })
  23.     }
  24.   }
  25. }
复制代码


  • @Monitor的参数需要为监听属性名的字符串,仅可以使用字符串字面量、const常量、enum枚举值作为参数。假如使用变量作为参数,仅会监听@Monitor初始化时,变量值所对应的属性。当更改变量时,@Monitor无法及时改变监听的属性,即@Monitor监听的目标属性从初始化时便已经确定,无法动态更改。不建议开辟者使用变量作为@Monitor的参数进行初始化。
  1. const t2: string = "t2"; // const常量
  2. enum ENUM {
  3.   T3 = "t3" // enum枚举值
  4. };
  5. let t4: string = "t4"; // 变量
  6. @ObservedV2
  7. class Info {
  8.   @Trace t1: number = 0;
  9.   @Trace t2: number = 0;
  10.   @Trace t3: number = 0;
  11.   @Trace t4: number = 0;
  12.   @Trace t5: number = 0;
  13.   @Monitor("t1") // 字符串字面量
  14.   onT1Change(monitor: IMonitor) {
  15.     console.log(`t1 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  16.   }
  17.   @Monitor(t2)
  18.   onT2Change(monitor: IMonitor) {
  19.     console.log(`t2 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  20.   }
  21.   @Monitor(ENUM.T3)
  22.   onT3Change(monitor: IMonitor) {
  23.     console.log(`t3 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  24.   }
  25.   @Monitor(t4)
  26.   onT4Change(monitor: IMonitor) {
  27.     console.log(`t4 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  28.   }
  29. }
  30. @Entry
  31. @ComponentV2
  32. struct Index {
  33.   info: Info = new Info();
  34.   build() {
  35.     Column() {
  36.       Button("Change t1")
  37.         .onClick(() => {
  38.           this.info.t1++; // 能够触发onT1Change方法
  39.         })
  40.       Button("Change t2")
  41.         .onClick(() => {
  42.           this.info.t2++; // 能够触发onT2Change方法
  43.         })
  44.       Button("Change t3")
  45.         .onClick(() => {
  46.           this.info.t3++; // 能够触发onT3Change方法
  47.         })
  48.       Button("Change t4")
  49.         .onClick(() => {
  50.           this.info.t4++; // 能够触发onT4Change方法
  51.         })
  52.       Button("Change var t4 to t5")
  53.         .onClick(() => {
  54.           t4 = "t5"; // 更改变量值为"t5"
  55.         })
  56.       Button("Change t5")
  57.         .onClick(() => {
  58.           this.info.t5++; // onT4Change仍监听t4,不会触发
  59.         })
  60.       Button("Change t4 again")
  61.         .onClick(() => {
  62.           this.info.t4++; // 能够触发onT4Change方法
  63.         })
  64.     }
  65.   }
  66. }
复制代码


  • 建议开辟者避免在@Monitor中再次更改被监听的属性,这会导致无限循环。
  1. @ObservedV2
  2. class Info {
  3.   @Trace count: number = 0;
  4.   @Monitor("count")
  5.   onCountChange(monitor: IMonitor) {
  6.     this.count++; // 应避免这种写法,会导致无限循环
  7.   }
  8. }
复制代码
@Monitor与@Watch对比

@Monitor与@Watch的用法、功能对比如下:
@Watch@Monitor参数回调方法名监听状态变量名、属性名监听目标数只能监听单个状态变量能同时监听多个状态变量监听能力跟随状态变量观察能力(一层)跟随状态变量观察能力(深层)可否获取变化前的值不能获取变化前的值能获取变化前的值监听条件监听对象为状态变量监听对象为状态变量或为@Trace装饰的类成员属性使用限定仅能在@Component装饰的自界说组件中使用能在@ComponentV2装饰的自界说组件中使用,也能在@ObservedV2装饰的类中使用 使用场景

监听深层属性变化

@Monitor可以监听深层属性的变化,并能够根据更改前后的值做分类处置处罚。
下面的示例中监听了属性value的变化,并根据变化的幅度改变Text组件显示的样式。
  1. @ObservedV2
  2. class Info {
  3.   @Trace value: number = 50;
  4. }
  5. @ObservedV2
  6. class UIStyle {
  7.   info: Info = new Info();
  8.   @Trace color: Color = Color.Black;
  9.   @Trace fontSize: number = 45;
  10.   @Monitor("info.value")
  11.   onValueChange(monitor: IMonitor) {
  12.     let lastValue: number = monitor.value()?.before as number;
  13.     let curValue: number = monitor.value()?.now as number;
  14.     if (lastValue != 0) {
  15.       let diffPercent: number = (curValue - lastValue) / lastValue;
  16.       if (diffPercent > 0.1) {
  17.         this.color = Color.Red;
  18.         this.fontSize = 50;
  19.       } else if (diffPercent < -0.1) {
  20.         this.color = Color.Green;
  21.         this.fontSize = 40;
  22.       } else {
  23.         this.color = Color.Black;
  24.         this.fontSize = 45;
  25.       }
  26.     }
  27.   }
  28. }
  29. @Entry
  30. @ComponentV2
  31. struct Index {
  32.   textStyle: UIStyle = new UIStyle();
  33.   build() {
  34.     Column() {
  35.       Text(`Important Value: ${this.textStyle.info.value}`)
  36.         .fontColor(this.textStyle.color)
  37.         .fontSize(this.textStyle.fontSize)
  38.       Button("change!")
  39.         .onClick(() => {
  40.           this.textStyle.info.value = Math.floor(Math.random() * 100) + 1;
  41.         })
  42.     }
  43.   }
  44. }
复制代码
常见问题

自界说组件中@Monitor对变量监听的收效及失效时间

当@Monitor界说在@ComponentV2装饰的自界说组件中时,@Monitor会在状态变量初始化完成之后收效,并在组件销毁时失效。
  1. @ObservedV2
  2. class Info {
  3.   @Trace message: string = "not initialized";
  4.   constructor() {
  5.     console.log("in constructor message change to initialized");
  6.     this.message = "initialized";
  7.   }
  8. }
  9. @ComponentV2
  10. struct Child {
  11.   @Param info: Info = new Info();
  12.   @Monitor("info.message")
  13.   onMessageChange(monitor: IMonitor) {
  14.     console.log(`Child message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  15.   }
  16.   aboutToAppear(): void {
  17.     this.info.message = "Child aboutToAppear";
  18.   }
  19.   aboutToDisappear(): void {
  20.     console.log("Child aboutToDisappear");
  21.     this.info.message = "Child aboutToDisappear";
  22.   }
  23.   build() {
  24.     Column() {
  25.       Text("Child")
  26.       Button("change message in Child")
  27.         .onClick(() => {
  28.           this.info.message = "Child click to change Message";
  29.         })
  30.     }
  31.     .borderColor(Color.Red)
  32.     .borderWidth(2)
  33.   }
  34. }
  35. @Entry
  36. @ComponentV2
  37. struct Index {
  38.   @Local info: Info = new Info();
  39.   @Local flag: boolean = false;
  40.   @Monitor("info.message")
  41.   onMessageChange(monitor: IMonitor) {
  42.     console.log(`Index message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  43.   }
  44.   build() {
  45.     Column() {
  46.       Button("show/hide Child")
  47.         .onClick(() => {
  48.           this.flag = !this.flag
  49.         })
  50.       Button("change message in Index")
  51.         .onClick(() => {
  52.           this.info.message = "Index click to change Message";
  53.         })
  54.       if (this.flag) {
  55.         Child({ info: this.info })
  56.       }
  57.     }
  58.   }
  59. }
复制代码
在上面的例子中,可以通过创建和销毁Child组件来观察界说在自界说组件中的@Monitor的收效和失效时机。推荐按如下顺序进行操作:


  • 当Index组件创建Info类实例时,日志输出in constructor message change to initialized。此时Index组件的@Monitor还未初始化成功,因此不会监听到message的变化。
  • 当Index组件创建完成,页面加载完成后,点击按钮“change message in Index”,此时Index组件中的@Monitor能够监听到变化,日志输出Index message change from initialized to Index click to change Message。
  • 点击按钮“show/hide Child”,创建Child组件,在Child组件初始化@Param装饰的变量以及@Monitor之后,调用Child组件的aboutToAppear回调,改变message。此时Index组件与Child组件的@Monitor均能监听到变化,日志输出Index message change from Index click to change Message to Child aboutToAppear以及Child message change from Index click to change Message to Child aboutToAppear。
  • 点击按钮“change message in Child”,改变message。此时Index组件与Child组件的@Monitor均能监听到变化,日志输出Index message change from Child aboutToAppear to Child click to change Message以及Child message change from Child aboutToAppear to Child click to change Message。
  • 点击按钮”show/hide Child“,销毁Child组件,调用Child组件的aboutToDisappear回调,改变message。此时Index组件与Child组件的@Monitor均能监听到变化,日志输出Child aboutToDisappear,Index message change from Child click to change Message to Child aboutToDisappear以及Child message change from Child click to change Message to Child aboutToDisappear。
  • 点击按钮“change message in Index”,改变message。此时Child组件已销毁,其注册的@Monitor监听也被解注册,仅有Index组件的@Monitor能够监听到变化,日志输出Index message change from Child aboutToDisappear to Index click to change Message。
这表明Child组件中界说的@Monitor监听随着Child组件的创建初始化收效,随着Child组件的销毁失效。
类中@Monitor对变量监听的收效及失效时间

当@Monitor界说在@ObservedV2装饰的类中时,@Monitor会在类创建完成后收效,在类销毁时失效。
  1. @ObservedV2
  2. class Info {
  3.   @Trace message: string = "not initialized";
  4.   constructor() {
  5.     this.message = "initialized";
  6.   }
  7.   @Monitor("message")
  8.   onMessageChange(monitor: IMonitor) {
  9.     console.log(`message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
  10.   }
  11. }
  12. @Entry
  13. @ComponentV2
  14. struct Index {
  15.   info: Info = new Info();
  16.   aboutToAppear(): void {
  17.     this.info.message = "Index aboutToAppear";
  18.   }
  19.   build() {
  20.     Column() {
  21.       Button("change message")
  22.         .onClick(() => {
  23.           this.info.message = "Index click to change message";
  24.         })
  25.     }
  26.   }
  27. }
复制代码
上面的例子中,@Monitor会在info创建完成后收效,这个时机晚于类的constructor,早于自界说组件的aboutToAppear。当界面加载完成后,点击“change message”,修改message变量。此时日志输出信息如下:
  1. message change from initialized to Index aboutToAppear
  2. message change from Index aboutToAppear to Index click to change message
复制代码
类中界说的@Monitor随着类的销毁失效。而由于类的实际销毁开释依靠于垃圾回收机制,因此会出现即使所在自界说组件已经销毁,类却还未及时销毁,导致类中界说的@Monitor仍在监听变化的情况。
  1. @ObservedV2
  2. class InfoWrapper {
  3.   info?: Info;
  4.   constructor(info: Info) {
  5.     this.info = info;
  6.   }
  7.   @Monitor("info.age")
  8.   onInfoAgeChange(monitor: IMonitor) {
  9.     console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
  10.   }
  11. }
  12. @ObservedV2
  13. class Info {
  14.   @Trace age: number;
  15.   constructor(age: number) {
  16.     this.age = age;
  17.   }
  18. }
  19. @ComponentV2
  20. struct Child {
  21.   @Param @Require infoWrapper: InfoWrapper;
  22.   aboutToDisappear(): void {
  23.     console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
  24.   }
  25.   build() {
  26.     Column() {
  27.       Text(`${this.infoWrapper.info?.age}`)
  28.     }
  29.   }
  30. }
  31. @Entry
  32. @ComponentV2
  33. struct Index {
  34.   dataArray: Info[] = [];
  35.   @Local showFlag: boolean = true;
  36.   aboutToAppear(): void {
  37.     for (let i = 0; i < 5; i++) {
  38.       this.dataArray.push(new Info(i));
  39.     }
  40.   }
  41.   build() {
  42.     Column() {
  43.       Button("change showFlag")
  44.         .onClick(() => {
  45.           this.showFlag = !this.showFlag;
  46.         })
  47.       Button("change number")
  48.         .onClick(() => {
  49.           console.log("click to change age")
  50.           this.dataArray.forEach((info: Info) => {
  51.             info.age += 100;
  52.           })
  53.         })
  54.       if (this.showFlag) {
  55.         Column() {
  56.           Text("Childs")
  57.           ForEach(this.dataArray, (info: Info) => {
  58.             Child({ infoWrapper: new InfoWrapper(info) })
  59.           })
  60.         }
  61.         .borderColor(Color.Red)
  62.         .borderWidth(2)
  63.       }
  64.     }
  65.   }
  66. }
复制代码
在上面的例子中,当点击“change showFlag”切换if组件的条件时,Child组件会被销毁。此时,点击“change number”修改age的值时,可以通过日志观察到InfoWrapper中界说的@Monitor回调仍然被触发了。这是因为此时自界说组件Child虽然实验了aboutToDisappear,但是其成员变量infoWrapper还没有被立即回收,当变量发生变化时,依然能够调用到infoWrapper中界说的onInfoAgeChange方法,所以从征象上看@Monitor回调仍会被触发。
借助垃圾回收机制去取消@Monitor的监听是不稳定的,开辟者可以采用以下两种方式去管理@Monitor的失效时间:
1、将@Monitor界说在自界说组件中。由于自界说组件在销毁时,状态管理框架会手动取消@Monitor的监听,因此在自界说组件调用完aboutToDisappear,尽管自界说组件的数据不一定已经被开释,但@Monitor回调已不会再被触发。
  1. @ObservedV2
  2. class InfoWrapper {
  3.   info?: Info;
  4.   constructor(info: Info) {
  5.     this.info = info;
  6.   }
  7. }
  8. @ObservedV2
  9. class Info {
  10.   @Trace age: number;
  11.   constructor(age: number) {
  12.     this.age = age;
  13.   }
  14. }
  15. @ComponentV2
  16. struct Child {
  17.   @Param @Require infoWrapper: InfoWrapper;
  18.   @Monitor("infoWrapper.info.age")
  19.   onInfoAgeChange(monitor: IMonitor) {
  20.     console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
  21.   }
  22.   aboutToDisappear(): void {
  23.     console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
  24.   }
  25.   build() {
  26.     Column() {
  27.       Text(`${this.infoWrapper.info?.age}`)
  28.     }
  29.   }
  30. }
  31. @Entry
  32. @ComponentV2
  33. struct Index {
  34.   dataArray: Info[] = [];
  35.   @Local showFlag: boolean = true;
  36.   aboutToAppear(): void {
  37.     for (let i = 0; i < 5; i++) {
  38.       this.dataArray.push(new Info(i));
  39.     }
  40.   }
  41.   build() {
  42.     Column() {
  43.       Button("change showFlag")
  44.         .onClick(() => {
  45.           this.showFlag = !this.showFlag;
  46.         })
  47.       Button("change number")
  48.         .onClick(() => {
  49.           console.log("click to change age")
  50.           this.dataArray.forEach((info: Info) => {
  51.             info.age += 100;
  52.           })
  53.         })
  54.       if (this.showFlag) {
  55.         Column() {
  56.           Text("Childs")
  57.           ForEach(this.dataArray, (info: Info) => {
  58.             Child({ infoWrapper: new InfoWrapper(info) })
  59.           })
  60.         }
  61.         .borderColor(Color.Red)
  62.         .borderWidth(2)
  63.       }
  64.     }
  65.   }
  66. }
复制代码
2、自动置空监听的对象。当自界说组件即将销毁时,自动置空@Monitor的监听目标,这样@Monitor无法再监听原监听目标的变化,达到取消@Monitor监听的结果。
  1. @ObservedV2
  2. class InfoWrapper {
  3.   info?: Info;
  4.   constructor(info: Info) {
  5.     this.info = info;
  6.   }
  7.   @Monitor("info.age")
  8.   onInfoAgeChange(monitor: IMonitor) {
  9.     console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`)
  10.   }
  11. }
  12. @ObservedV2
  13. class Info {
  14.   @Trace age: number;
  15.   constructor(age: number) {
  16.     this.age = age;
  17.   }
  18. }
  19. @ComponentV2
  20. struct Child {
  21.   @Param @Require infoWrapper: InfoWrapper;
  22.   aboutToDisappear(): void {
  23.     console.log("Child aboutToDisappear", this.infoWrapper.info?.age)
  24.     this.infoWrapper.info = undefined; // 使InfoWrapper对info.age的监听失效
  25.   }
  26.   build() {
  27.     Column() {
  28.       Text(`${this.infoWrapper.info?.age}`)
  29.     }
  30.   }
  31. }
  32. @Entry
  33. @ComponentV2
  34. struct Index {
  35.   dataArray: Info[] = [];
  36.   @Local showFlag: boolean = true;
  37.   aboutToAppear(): void {
  38.     for (let i = 0; i < 5; i++) {
  39.       this.dataArray.push(new Info(i));
  40.     }
  41.   }
  42.   build() {
  43.     Column() {
  44.       Button("change showFlag")
  45.         .onClick(() => {
  46.           this.showFlag = !this.showFlag;
  47.         })
  48.       Button("change number")
  49.         .onClick(() => {
  50.           console.log("click to change age")
  51.           this.dataArray.forEach((info: Info) => {
  52.             info.age += 100;
  53.           })
  54.         })
  55.       if (this.showFlag) {
  56.         Column() {
  57.           Text("Childs")
  58.           ForEach(this.dataArray, (info: Info) => {
  59.             Child({ infoWrapper: new InfoWrapper(info) })
  60.           })
  61.         }
  62.         .borderColor(Color.Red)
  63.         .borderWidth(2)
  64.       }
  65.     }
  66.   }
  67. }
复制代码
精确设置@Monitor入参

由于@Monitor无法对入参做编译时校验,当前存在以下写法不符合@Monitor监听条件但@Monitor仍会触发的情况。开辟者应当精确传入@Monitor入参,不传入非状态变量,避免造成功能非常或活动体现不符合预期。
【反例1】
  1. @ObservedV2
  2. class Info {
  3.   name: string = "John";
  4.   @Trace age: number = 24;
  5.   @Monitor("age", "name") // 同时监听状态变量age和非状态变量name
  6.   onPropertyChange(monitor: IMonitor) {
  7.     monitor.dirty.forEach((path: string) => {
  8.       console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
  9.     })
  10.   }
  11. }
  12. @Entry
  13. @ComponentV2
  14. struct Index {
  15.   info: Info = new Info();
  16.   build() {
  17.     Column() {
  18.       Button("change age&name")
  19.         .onClick(() => {
  20.           this.info.age = 25; // 同时改变状态变量age和非状态变量name
  21.           this.info.name = "Johny";
  22.         })
  23.     }
  24.   }
  25. }
复制代码
上面的代码中,当点击按钮同时更改状态变量age和非状态变量name时,会输出以下日志:
  1. property path:age change from 24 to 25
  2. property path:name change from John to Johny
复制代码
实际上name属性本身并不是可被观测的变量,不应被参加到@Monitor的入参当中。建议开辟者去除对name属性的监听或者将给name加上@Trace装饰成为状态变量。
【正例1】
  1. @ObservedV2
  2. class Info {
  3.   name: string = "John";
  4.   @Trace age: number = 24;
  5.   @Monitor("age") // 仅监听状态变量age
  6.   onPropertyChange(monitor: IMonitor) {
  7.     monitor.dirty.forEach((path: string) => {
  8.       console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
  9.     })
  10.   }
  11. }
  12. @Entry
  13. @ComponentV2
  14. struct Index {
  15.   info: Info = new Info();
  16.   build() {
  17.     Column() {
  18.       Button("change age&name")
  19.         .onClick(() => {
  20.           this.info.age = 25; // 状态变量age改变
  21.           this.info.name = "Johny";
  22.         })
  23.     }
  24.   }
  25. }
复制代码
【反例2】
  1. @ObservedV2
  2. class Info {
  3.   name: string = "John";
  4.   @Trace age: number = 24;
  5.   get myAge() {
  6.     return this.age; // age为状态变量
  7.   }
  8.   @Monitor("myAge") // 监听非@Computed装饰的getter访问器
  9.   onPropertyChange() {
  10.     console.log("age changed");
  11.   }
  12. }
  13. @Entry
  14. @ComponentV2
  15. struct Index {
  16.   info: Info = new Info();
  17.   build() {
  18.     Column() {
  19.       Button("change age")
  20.         .onClick(() => {
  21.           this.info.age = 25; // 状态变量age改变
  22.         })
  23.     }
  24.   }
  25. }
复制代码
上面的代码中,@Monitor的入参为一个getter访问器的名字,但该getter访问器本身并未被@Computed装饰,不是一个可被监听的变量。但由于使用了状态变量参与了计算,在状态变量变化后,myAge也被以为发生了变化,因此触发了@Monitor回调。建议开辟者给myAge添加@Computed装饰器或当getter访问器直接返回状态变量时,不监听getter访问器而是直接监听状态变量本身。
【正例2】
将myAge变为状态变量:
  1. @ObservedV2
  2. class Info {
  3.   name: string = "John";
  4.   @Trace age: number = 24;
  5.   @Computed // 给myAge添加@Computed成为状态变量
  6.   get myAge() {
  7.     return this.age;
  8.   }
  9.   @Monitor("myAge") // 监听@Computed装饰的getter访问器
  10.   onPropertyChange() {
  11.     console.log("age changed");
  12.   }
  13. }
  14. @Entry
  15. @ComponentV2
  16. struct Index {
  17.   info: Info = new Info();
  18.   build() {
  19.     Column() {
  20.       Button("change age")
  21.         .onClick(() => {
  22.           this.info.age = 25; // 状态变量age改变
  23.         })
  24.     }
  25.   }
  26. }
复制代码
或直接监听状态变量本身:
  1. @ObservedV2
  2. class Info {
  3.   name: string = "John";
  4.   @Trace age: number = 24;
  5.   @Monitor("age") // 监听状态变量age
  6.   onPropertyChange() {
  7.     console.log("age changed");
  8.   }
  9. }
  10. @Entry
  11. @ComponentV2
  12. struct Index {
  13.   info: Info = new Info();
  14.   build() {
  15.     Column() {
  16.       Button("change age")
  17.         .onClick(() => {
  18.           this.info.age = 25; // 状态变量age改变
  19.         })
  20.     }
  21.   }
  22. }
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

乌市泽哥

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表