android2harmony 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/agents/self-tester.md +376 -0
  2. package/dist/index.js +152 -7
  3. package/dist/index.js.map +4 -4
  4. package/package.json +32 -29
  5. package/skills/hmos-incremental-ui-align/README.md +251 -0
  6. package/skills/hmos-incremental-ui-align/SKILL.md +365 -0
  7. package/skills/hmos-incremental-ui-align/diff_analysis.md +53 -0
  8. package/skills/hmos-incremental-ui-align/page_align.md +62 -0
  9. package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +38 -0
  10. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  11. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  12. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  13. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  14. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  15. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  16. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  17. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  18. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  19. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  20. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  21. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  22. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  23. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +206 -0
  24. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +373 -0
  25. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +158 -0
  26. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +750 -0
  27. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +1704 -0
  28. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1012 -0
  29. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +164 -0
  30. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +840 -0
  31. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +856 -0
  32. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +83 -0
  33. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +294 -0
  34. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1407 -0
  35. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +1220 -0
  36. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +47 -0
  37. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md +840 -0
  38. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +74 -0
  39. package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +34 -0
  40. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
  41. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  42. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  43. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +999 -0
  44. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +343 -0
  45. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -0
  46. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +977 -0
  47. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +188 -0
@@ -0,0 +1,1704 @@
1
+ # @Monitor装饰器:状态变量修改异步监听
2
+ ---
3
+ # @Monitor装饰器:状态变量修改异步监听
4
+ 为了增强状态管理框架对状态变量变化的监听能力,开发者可以使用@Monitor装饰器对状态变量进行监听。
5
+ @Monitor提供了对V2状态变量的监听。在阅读本文档前,建议提前阅读: [@ComponentV2](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式基本语法/自定义组件/创建自定义组件/arkts-create-custom-components.md) , [@ObservedV2和@Trace](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理数据对象的状态/@ObservedV2装饰器和@Trace装饰器:类属性变化观测/arkts-new-observedv2-and-trace.md) , [@Local](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理组件拥有的状态/@Local装饰器:组件内部状态/arkts-new-local.md) 。
6
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/4e/v3/Wx_STxJwRGq3RDVpN6zCMA/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260701T104555Z&HW-CC-Expire=86400&HW-CC-Sign=6AEE88FA989152AE9932A565DFA85D3F72EA18DDFE8A9092E9576AD48EC3FA94)
7
+ @Monitor装饰器从API version 12开始支持。
8
+ 从API version 12开始,该装饰器支持在元服务中使用。
9
+ 从API version 23开始,该装饰器支持在ArkTS卡片中使用。
10
+ 从API版本26.0.0开始,该装饰器新增支持通配符能力。
11
+ #### 概述
12
+ @Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力:
13
+ -
14
+ @Monitor装饰器支持在@ComponentV2装饰的自定义组件中使用,未被状态变量装饰器 [@Local](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理组件拥有的状态/@Local装饰器:组件内部状态/arkts-new-local.md) 、 [@Param](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理组件拥有的状态/@Param:组件外部输入/arkts-new-param.md) 、 [@Provider](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理组件拥有的状态/@Provider装饰器和@Consumer装饰器:跨组件层级双向同步/arkts-new-provider-and-consumer.md) 、 [@Consumer](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理组件拥有的状态/@Provider装饰器和@Consumer装饰器:跨组件层级双向同步/arkts-new-provider-and-consumer.md) 、 [@Computed](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理数据对象的状态/@Computed装饰器:计算属性/arkts-new-computed.md) 装饰的变量无法被@Monitor监听到变化。
15
+ -
16
+ @Monitor装饰器支持在类中与 [@ObservedV2、@Trace](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理数据对象的状态/@ObservedV2装饰器和@Trace装饰器:类属性变化观测/arkts-new-observedv2-and-trace.md) 配合使用,不允许在未被@ObservedV2装饰的类中使用@Monitor装饰器。未被@Trace装饰的属性无法被@Monitor监听到变化。
17
+ -
18
+ 当观测的属性变化时,@Monitor装饰器定义的回调方法将被调用。判断属性是否变化使用的是严格相等(===),当严格相等判断的结果是false(即不相等)的情况下,就会触发@Monitor的回调。当在一次事件中多次改变同一个属性时,将会使用初始值和最终值进行比较以判断是否变化。
19
+ -
20
+ 单个@Monitor装饰器能够同时监听多个属性的变化,当这些属性在一次事件中共同变化时,只会触发一次@Monitor的回调方法。
21
+ -
22
+ @Monitor装饰器具有深度监听的能力,能够监听嵌套类、多维数组、对象数组中指定项的变化。对于嵌套类、对象数组中成员属性变化的监听要求该类被@ObservedV2装饰且该属性被@Trace装饰。
23
+ -
24
+ 当@Monitor监听整个数组时,更改数组的某一项不会被监听到。无法监听内置类型(Array、Map、Date、Set)的API调用引起的变化。
25
+ -
26
+ 在继承类场景中,可以在父子组件中对同一个属性分别定义@Monitor进行监听,当属性变化时,父子组件中定义的@Monitor回调均会被调用。
27
+ -
28
+ 和 [@Watch装饰器](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V1)/管理组件拥有的状态/@Watch装饰器:状态变量更改通知/arkts-watch.md) 类似,开发者需要自己定义回调函数,区别在于@Watch装饰器将函数名作为参数,而@Monitor直接装饰回调函数。@Monitor与@Watch的对比可以查看 [@Monitor与@Watch的对比](#monitor与watch对比) 。
29
+ 从API版本26.0.0开始,支持配置 [MonitorDecoratorOptions](D:/code/APIDevice/output/md_output/harmonyos-references/应用框架/ArkUI(方舟UI框架)/ArkTS组件/状态管理与渲染控制/ts-state-management-watch-monitor.md) 来获得以下能力增强:
30
+ - 支持在监听路径中设置通配符“*”,用于模糊监听对象内部变化,包括@ObservedV2中任意@Trace属性变化,内置类型(Array、Map、Date、Set)的API调用引起的变化等。详情见[监听包含通配符的路径](#监听包含通配符的路径)。
31
+ - 对@Monitor部分能力进行修正,详情见[@Monitor使用配置项前后的对比](#monitor使用配置项前后的对比)。
32
+ #### 状态管理V1版本@Watch装饰器的局限性
33
+ 现有状态管理V1版本无法实现对对象、数组中某一单个属性或数组项变化的监听,且无法获取变化之前的值。
34
+ ```
35
+ import { hilog } from '@kit.PerformanceAnalysisKit';
36
+ @Observed
37
+ class Info {
38
+ public name: string = 'Tom';
39
+ public age: number = 25;
40
+ }
41
+ @Entry
42
+ @Component
43
+ struct Index {
44
+ @State @Watch('onInfoChange') info: Info = new Info();
45
+ @State @Watch('onNumArrChange') numArr: number[] = [1, 2, 3, 4, 5];
46
+ onInfoChange() {
47
+ hilog.info(0xFF00, 'testTag', '%{public}s', `info after change name: ${this.info.name}, age: ${this.info.age} `);
48
+ }
49
+ onNumArrChange() {
50
+ hilog.info(0xFF00, 'testTag', '%{public}s', `numArr after change ${this.numArr}`);
51
+ }
52
+ build() {
53
+ Row() {
54
+ Column() {
55
+ // 对象、数组中某一单个属性或数组项变化,不会触发UI刷新
56
+ Button('change info name')
57
+ .onClick(() => {
58
+ this.info.name = 'Jack';
59
+ })
60
+ Button('change info age')
61
+ .onClick(() => {
62
+ this.info.age = 30;
63
+ })
64
+ Button('change numArr[2]')
65
+ .onClick(() => {
66
+ this.numArr[2] = 5;
67
+ })
68
+ Button('change numArr[3]')
69
+ .onClick(() => {
70
+ this.numArr[3] = 6;
71
+ })
72
+ }
73
+ .width('100%')
74
+ }
75
+ .height('100%')
76
+ }
77
+ }
78
+ ```
79
+ 上述代码中,点击"change info name"更改info中的name属性或点击"change info age"更改age时,均会触发info注册的@Watch回调。点击"change numArr[2]"更改numArr中的第3个元素或点击"change numArr[3]"更改第4个元素时,均会触发numArr注册的@Watch回调。在这两个回调中,由于无法获取数据更改前的值,在业务逻辑更加复杂的场景下,无法准确知道是哪一个属性或元素发生了改变从而触发了@Watch事件,这不便于开发者对变量的更改进行准确监听。因此推出@Monitor装饰器实现对对象、数组中某一单个属性或数组项变化的监听,并且能够获取到变化之前的值。
80
+ #### 装饰器说明
81
+ | @Monitor属性装饰器 | 说明 |
82
+ | --- | --- |
83
+ | 装饰器参数 | API版本26.0.0之前,参数为字符串类型的对象属性名。从API版本26.0.0开始,第一个参数也可以为[MonitorDecoratorOptions](D:/code/APIDevice/output/md_output/harmonyos-references/应用框架/ArkUI(方舟UI框架)/ArkTS组件/状态管理与渲染控制/ts-state-management-watch-monitor.md)配置项。可同时监听多个对象属性,每个属性以逗号隔开,例如@Monitor('prop1', 'prop2')。可监听深层的属性变化,如多维数组中的某一个元素,嵌套对象或对象数组中的某一个属性。详见[监听变化](#监听变化)。 |
84
+ | 装饰对象 | @Monitor装饰成员方法。当监听的属性发生变化时,会触发该回调方法。该回调方法以[IMonitor类型](D:/code/APIDevice/output/md_output/harmonyos-references/应用框架/ArkUI(方舟UI框架)/ArkTS组件/状态管理与渲染控制/ts-state-management-watch-monitor.md)的变量作为参数,开发者可以从该参数中获取变化前后的相关信息。 |
85
+ #### 语法
86
+ ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/3b/v3/VZ85_zbFQrKmiSHgwdFMvQ/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260701T104555Z&HW-CC-Expire=86400&HW-CC-Sign=8ABE7527A7EBF1567A2C525B9C5B8DE68A4B62380F1DB3A835248E3EA8FCB008)
87
+ 为简化说明,下文将传入MonitorDecoratorOptions的@Monitor调用称为 **使用配置项的@Monitor** 。将未传入MonitorDecoratorOptions的@Monitor调用称为 **未使用配置项的@Monitor** 。
88
+ 未使用配置项的@Monitor语法:
89
+ ```typescript
90
+ @Monitor('path')
91
+ onValueChange(monitor: IMonitor) {
92
+ }
93
+ ```
94
+ 使用配置项的@Monitor语法:
95
+ ```
96
+ @Monitor({ enableWildcard: false }, 'path') // 使用配置项,显式配置不使能通配符
97
+ onValueChanged1(monitor: IMonitor) {
98
+ }
99
+ @Monitor({}, 'path.*') // 使用配置项,默认使能通配符,监听path对象内任意可观察变化
100
+ onValueChange2(monitor: IMonitor) {
101
+ }
102
+ @Monitor({ enableWildcard: true }, 'path.*') // 使用配置项,显式配置使能通配符
103
+ onValueChange3(monitor: IMonitor) {
104
+ }
105
+ ```
106
+ #### @Monitor使用配置项前后的对比
107
+ | 场景 | 未使用配置项的@Monitor | 使用配置项的@Monitor |
108
+ | --- | --- | --- |
109
+ | 使用通配符 | 不支持。 | 支持。 |
110
+ | 监听不可监听变量 | 存在被连带触发监听的可能,详情见[正确设置@Monitor入参](#正确设置monitor入参)。 | 忽略不可监听变量,对路径的监听变为互相独立的监听。 |
111
+ | 变量可访问性变化 | 仅记录变量可访问时的状态,无法正常处理变量变为不可访问的情况。 | 变量从可访问变为不可访问,或从不可访问变为可访问,均能正常处理。 |
112
+ 使用配置项的@Monitor在以上场景的表现,将与 [@SyncMonitor](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/状态管理(V2)/管理数据对象的状态/@SyncMonitor装饰器:状态变量修改同步监听/arkts-new-syncmonitor.md) 、 [addMonitor](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/辅助接口/addMonitor_clearMonitor接口:动态添加_取消监听/arkts-new-addmonitor-clearmonitor.md) 保持一致。
113
+ #### 接口说明
114
+ IMonitor类型、IMonitorValue<T>类型以及MonitorDecoratorOptions的接口说明参考API文档: [状态变量变化监听](D:/code/APIDevice/output/md_output/harmonyos-references/应用框架/ArkUI(方舟UI框架)/ArkTS组件/状态管理与渲染控制/ts-state-management-watch-monitor.md) 。
115
+ #### 监听变化
116
+ #### 在@ComponentV2装饰的自定义组件中使用@Monitor
117
+ 使用@Monitor监听的状态变量发生变化时,会触发@Monitor的回调方法。
118
+ -
119
+ @Monitor监听的变量需要被@Local、@Param、@Provider、@Consumer、@Computed装饰,未被状态变量装饰器装饰的变量在变化时无法被监听。@Monitor可以同时监听多个状态变量,这些变量名之间用","隔开。
120
+ ```
121
+ import { hilog } from '@kit.PerformanceAnalysisKit';
122
+ @Entry
123
+ @ComponentV2
124
+ struct Index {
125
+ @Local message: string = 'Hello World';
126
+ @Local name: string = 'Tom';
127
+ @Local age: number = 24;
128
+ @Monitor('message', 'name')
129
+ onStrChange(monitor: IMonitor) {
130
+ monitor.dirty.forEach((path: string) => {
131
+ hilog.info(0xFF00, 'testTag', '%{public}s',
132
+ `${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
133
+ });
134
+ }
135
+ build() {
136
+ Column() {
137
+ // 点击Button更新message和name,触发onStrChange回调
138
+ Button('change string')
139
+ .onClick(() => {
140
+ this.message += '!';
141
+ this.name = 'Jack';
142
+ })
143
+ }
144
+ }
145
+ }
146
+ ```
147
+ -
148
+ @Monitor监听的状态变量为类对象时,仅能监听对象整体的变化。监听类属性的变化需要类属性被@Trace装饰。
149
+ ```
150
+ import { hilog } from '@kit.PerformanceAnalysisKit';
151
+ class Info {
152
+ public name: string;
153
+ public age: number;
154
+ constructor(name: string, age: number) {
155
+ this.name = name;
156
+ this.age = age;
157
+ }
158
+ }
159
+ @Entry
160
+ @ComponentV2
161
+ struct Index {
162
+ @Local info: Info = new Info('Tom', 25);
163
+ @Monitor('info')
164
+ infoChange(monitor: IMonitor) {
165
+ hilog.info(0xFF00, 'testTag', '%{public}s', `info change`);
166
+ }
167
+ @Monitor('info.name')
168
+ infoPropertyChange(monitor: IMonitor) {
169
+ hilog.info(0xFF00, 'testTag', '%{public}s', `info name change`);
170
+ }
171
+ build() {
172
+ Column() {
173
+ Text(`name: ${this.info.name}, age: ${this.info.age}`)
174
+ Button('change info')
175
+ .onClick(() => {
176
+ this.info = new Info('Lucy', 18); // 能够监听到
177
+ })
178
+ Button('change info.name')
179
+ .onClick(() => {
180
+ this.info.name = 'Jack'; // 监听不到
181
+ })
182
+ }
183
+ }
184
+ }
185
+ ```
186
+ #### 在@ObservedV2装饰的类中使用@Monitor
187
+ 使用@Monitor监听的属性发生变化时,会触发@Monitor的回调方法。
188
+ -
189
+ @Monitor监听的对象属性需要被@Trace装饰,未被@Trace装饰的属性的变化无法被监听。@Monitor可以同时监听多个属性,这些属性之间用","隔开。
190
+ ```
191
+ import { hilog } from '@kit.PerformanceAnalysisKit';
192
+ @ObservedV2
193
+ class Info {
194
+ @Trace public name: string = 'Tom';
195
+ @Trace public region: string = 'North';
196
+ @Trace public job: string = 'Teacher';
197
+ public age: number = 25;
198
+ // name被@Trace装饰,能够监听变化
199
+ @Monitor('name')
200
+ onNameChange(monitor: IMonitor) {
201
+ hilog.info(0xFF00, 'testTag', '%{public}s',
202
+ `name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
203
+ }
204
+ // age未被@Trace装饰,不能监听变化
205
+ @Monitor('age')
206
+ onAgeChange(monitor: IMonitor) {
207
+ hilog.info(0xFF00, 'testTag', '%{public}s',
208
+ `age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
209
+ }
210
+ // region与job均被@Trace装饰,能够监听变化
211
+ @Monitor('region', 'job')
212
+ onChange(monitor: IMonitor) {
213
+ monitor.dirty.forEach((path: string) => {
214
+ hilog.info(0xFF00, 'testTag', '%{public}s',
215
+ `${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
216
+ })
217
+ }
218
+ }
219
+ @Entry
220
+ @ComponentV2
221
+ struct Index {
222
+ info: Info = new Info();
223
+ build() {
224
+ Column() {
225
+ Button('change name')
226
+ .onClick(() => {
227
+ this.info.name = 'Jack'; // 能够触发onNameChange方法
228
+ })
229
+ Button('change age')
230
+ .onClick(() => {
231
+ this.info.age = 26; // 不能够触发onAgeChange方法
232
+ })
233
+ Button('change region')
234
+ .onClick(() => {
235
+ this.info.region = 'South'; // 能够触发onChange方法
236
+ })
237
+ Button('change job')
238
+ .onClick(() => {
239
+ this.info.job = 'Driver'; // 能够触发onChange方法
240
+ })
241
+ }
242
+ }
243
+ }
244
+ ```
245
+ -
246
+ @Monitor可以监听深层属性的变化,该深层属性需要被@Trace装饰。
247
+ ```
248
+ import { hilog } from '@kit.PerformanceAnalysisKit';
249
+ @ObservedV2
250
+ class Inner {
251
+ @Trace public num: number = 0;
252
+ }
253
+ @ObservedV2
254
+ class Outer {
255
+ public inner: Inner = new Inner();
256
+ @Monitor('inner.num')
257
+ onChange(monitor: IMonitor) {
258
+ hilog.info(0xFF00, 'testTag', '%{public}s',
259
+ `inner.num change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
260
+ }
261
+ }
262
+ @Entry
263
+ @ComponentV2
264
+ struct Index {
265
+ outer: Outer = new Outer();
266
+ build() {
267
+ Column() {
268
+ Button('change num')
269
+ .onClick(() => {
270
+ this.outer.inner.num = 100; // 能够触发onChange方法
271
+ })
272
+ }
273
+ }
274
+ }
275
+ ```
276
+ -
277
+ 在继承类场景下,可以在继承链中对同一个属性进行多次监听。
278
+ ```
279
+ import { hilog } from '@kit.PerformanceAnalysisKit';
280
+ @ObservedV2
281
+ class Base {
282
+ @Trace public name: string;
283
+ // 基类监听name属性
284
+ @Monitor('name')
285
+ onBaseNameChange(monitor: IMonitor) {
286
+ hilog.info(0xFF00, 'testTag', '%{public}s', `Base Class name change`);
287
+ }
288
+ constructor(name: string) {
289
+ this.name = name;
290
+ }
291
+ }
292
+ @ObservedV2
293
+ class Derived extends Base {
294
+ // 继承类监听name属性
295
+ @Monitor('name')
296
+ onDerivedNameChange(monitor: IMonitor) {
297
+ hilog.info(0xFF00, 'testTag', '%{public}s', `Derived Class name change`);
298
+ }
299
+ constructor(name: string) {
300
+ super(name);
301
+ }
302
+ }
303
+ @Entry
304
+ @ComponentV2
305
+ struct Index {
306
+ derived: Derived = new Derived('AAA');
307
+ build() {
308
+ Column() {
309
+ Button('change name')
310
+ .onClick(() => {
311
+ this.derived.name = 'BBB'; // 能够先后触发onBaseNameChange、onDerivedNameChange方法
312
+ })
313
+ }
314
+ }
315
+ }
316
+ ```
317
+ #### 通用监听能力
318
+ @Monitor还有一些通用的监听能力。
319
+ -
320
+ @Monitor支持对数组中的项进行监听,包括多维数组,对象数组。@Monitor无法监听内置类型(Array、Map、Date、Set)的API调用引起的变化。当@Monitor监听数组整体时,只能观测到数组整体的赋值。可以通过监听数组的长度变化来判断数组是否有插入、删除等变化。当前仅支持使用"."的方式表达深层属性、数组项的监听。
321
+ ```
322
+ import { hilog } from '@kit.PerformanceAnalysisKit';
323
+ @ObservedV2
324
+ class Info {
325
+ @Trace public name: string;
326
+ @Trace public age: number;
327
+ constructor(name: string, age: number) {
328
+ this.name = name;
329
+ this.age = age;
330
+ }
331
+ }
332
+ @ObservedV2
333
+ class ArrMonitor {
334
+ @Trace public dimensionTwo: number[][] = [[1, 1, 1], [2, 2, 2], [3, 3, 3]];
335
+ @Trace public dimensionThree: number[][][] = [[[1], [2], [3]], [[4], [5], [6]], [[7], [8], [9]]];
336
+ @Trace public infoArr: Info[] = [new Info('Jack', 24), new Info('Lucy', 18)];
337
+ // dimensionTwo为二维简单类型数组,且被@Trace装饰,能够观测里面的元素变化
338
+ @Monitor('dimensionTwo.0.0', 'dimensionTwo.1.1')
339
+ onDimensionTwoChange(monitor: IMonitor) {
340
+ monitor.dirty.forEach((path: string) => {
341
+ hilog.info(0xFF00, 'testTag', '%{public}s',
342
+ `dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
343
+ })
344
+ }
345
+ // dimensionThree为三维简单类型数组,且被@Trace装饰,能够观测里面的元素变化
346
+ @Monitor('dimensionThree.0.0.0', 'dimensionThree.1.1.0')
347
+ onDimensionThreeChange(monitor: IMonitor) {
348
+ monitor.dirty.forEach((path: string) => {
349
+ hilog.info(0xFF00, 'testTag', '%{public}s',
350
+ `dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
351
+ })
352
+ }
353
+ // Info类中属性name、age均被@Trace装饰,能够监听到变化
354
+ @Monitor('infoArr.0.name', 'infoArr.1.age')
355
+ onInfoArrPropertyChange(monitor: IMonitor) {
356
+ monitor.dirty.forEach((path: string) => {
357
+ hilog.info(0xFF00, 'testTag', '%{public}s',
358
+ `infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
359
+ })
360
+ }
361
+ // infoArr被@Trace装饰,能够监听到infoArr整体赋值的变化
362
+ @Monitor('infoArr')
363
+ onInfoArrChange(monitor: IMonitor) {
364
+ hilog.info(0xFF00, 'testTag', '%{public}s', `infoArr whole change`);
365
+ }
366
+ // 能够监听到infoArr的长度变化
367
+ @Monitor('infoArr.length')
368
+ onInfoArrLengthChange(monitor: IMonitor) {
369
+ hilog.info(0xFF00, 'testTag', '%{public}s', `infoArr length change`);
370
+ }
371
+ }
372
+ @Entry
373
+ @ComponentV2
374
+ struct Index {
375
+ arrMonitor: ArrMonitor = new ArrMonitor();
376
+ build() {
377
+ Column() {
378
+ Button('Change dimensionTwo')
379
+ .onClick(() => {
380
+ // 能够触发onDimensionTwoChange方法
381
+ this.arrMonitor.dimensionTwo[0][0]++;
382
+ this.arrMonitor.dimensionTwo[1][1]++;
383
+ })
384
+ Button('Change dimensionThree')
385
+ .onClick(() => {
386
+ // 能够触发onDimensionThreeChange方法
387
+ this.arrMonitor.dimensionThree[0][0][0]++;
388
+ this.arrMonitor.dimensionThree[1][1][0]++;
389
+ })
390
+ Button('Change info property')
391
+ .onClick(() => {
392
+ // 能够触发onInfoArrPropertyChange方法
393
+ this.arrMonitor.infoArr[0].name = 'Tom';
394
+ this.arrMonitor.infoArr[1].age = 19;
395
+ })
396
+ Button('Change whole infoArr')
397
+ .onClick(() => {
398
+ // 能够触发onInfoArrChange、onInfoArrPropertyChange、onInfoArrLengthChange方法
399
+ this.arrMonitor.infoArr = [new Info('Cindy', 8)];
400
+ })
401
+ Button('Push new info to infoArr')
402
+ .onClick(() => {
403
+ // 能够触发onInfoArrPropertyChange、onInfoArrLengthChange方法
404
+ this.arrMonitor.infoArr.push(new Info('David', 50));
405
+ })
406
+ }
407
+ }
408
+ }
409
+ ```
410
+ -
411
+ 对象整体改变,但监听的属性不变时,不触发@Monitor回调。
412
+ 下面的示例按照Step1-Step2-Step3的顺序点击,表现为代码注释中的行为。
413
+ 如果只点击Step2或Step3,改变name、age的值,此时会触发onNameChange和onAgeChange方法。
414
+ ```
415
+ import { hilog } from '@kit.PerformanceAnalysisKit';
416
+ @ObservedV2
417
+ class Info {
418
+ @Trace public person: Person;
419
+ @Monitor('person.name')
420
+ onNameChange(monitor: IMonitor) {
421
+ hilog.info(0xFF00, 'testTag', '%{public}s',
422
+ `name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
423
+ }
424
+ @Monitor('person.age')
425
+ onAgeChange(monitor: IMonitor) {
426
+ hilog.info(0xFF00, 'testTag', '%{public}s',
427
+ `age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
428
+ }
429
+ constructor(name: string, age: number) {
430
+ this.person = new Person(name, age);
431
+ }
432
+ }
433
+ @ObservedV2
434
+ class Person {
435
+ @Trace public name: string;
436
+ @Trace public age: number;
437
+ constructor(name: string, age: number) {
438
+ this.name = name;
439
+ this.age = age;
440
+ }
441
+ }
442
+ @Entry
443
+ @ComponentV2
444
+ struct Index {
445
+ info: Info = new Info('Tom', 25);
446
+ build() {
447
+ Column() {
448
+ Button('Step1: Only change name')
449
+ .onClick(() => {
450
+ this.info.person = new Person('Jack', 25); // 能够触发onNameChange方法,不触发onAgeChange方法
451
+ })
452
+ Button('Step2: Only change age')
453
+ .onClick(() => {
454
+ this.info.person = new Person('Jack', 18); // 能够触发onAgeChange方法,不触发onNameChange方法
455
+ })
456
+ Button('Step3: Change name and age')
457
+ .onClick(() => {
458
+ this.info.person = new Person('Lucy', 19); // 能够触发onNameChange、onAgeChange方法
459
+ })
460
+ }
461
+ }
462
+ }
463
+ ```
464
+ -
465
+ 在一次事件中多次改变被@Monitor监听的属性,以最后一次修改为准。
466
+ ```
467
+ import { hilog } from '@kit.PerformanceAnalysisKit';
468
+ @ObservedV2
469
+ class Frequency {
470
+ @Trace public count: number = 0;
471
+ @Monitor('count')
472
+ onCountChange(monitor: IMonitor) {
473
+ hilog.info(0xFF00, 'testTag', '%{public}s',
474
+ `count change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
475
+ }
476
+ }
477
+ @Entry
478
+ @ComponentV2
479
+ struct Index {
480
+ frequency: Frequency = new Frequency();
481
+ build() {
482
+ Column() {
483
+ Button('change count to 1000')
484
+ .onClick(() => {
485
+ for (let i = 1; i <= 1000; i++) {
486
+ this.frequency.count = i;
487
+ }
488
+ })
489
+ Button('change count to 0 then to 1000')
490
+ .onClick(() => {
491
+ for (let i = 999; i >= 0; i--) {
492
+ this.frequency.count = i;
493
+ }
494
+ this.frequency.count = 1000; // 最终不触发onCountChange方法
495
+ })
496
+ }
497
+ }
498
+ }
499
+ ```
500
+ 在点击按钮"change count to 1000"后,会触发一次onCountChange方法,并输出日志"count change from 0 to 1000"。在点击按钮"change count to 0 then to 1000"后,由于事件前后属性count的值并没有改变,都为1000,所以不触发onCountChange方法。
501
+ #### 监听包含通配符的路径
502
+ 从API版本26.0.0开始,@Monitor支持通配符能力。当使用配置项MonitorDecoratorOptions时,将默认开启通配符支持。通配符可以作为路径中的后缀,监听该路径最后确定值中的变化。该变化包括如@Trace属性的变化、内置类型(Array、Map、Set、Date)的API调用引起的变化等。
503
+ 通配符路径的语法规则为:
504
+ - 通配符只能出现在路径末尾。
505
+ - 通配符不能出现在路径开头,也不能出现在路径中间。
506
+ - 一个路径中最多仅可以出现一个通配符。
507
+ 合法的通配符路径示例为:
508
+ | 路径 | 说明 |
509
+ | --- | --- |
510
+ | obj.* | obj为@ObservedV2装饰的对象。监听该路径的@Monitor将在以下情况触发:1、对obj整体赋值。2、obj任意@Trace属性变化。 |
511
+ | arr.* | arr为可观察数组。监听该路径的@Monitor将在以下情况触发:1、对arr整体赋值。2、arr任意元素变化或数组长度变化。3、调用数组的API(如push、pop、sort、fill、copyWithin等)。 |
512
+ | obj.objA.* | objA为@ObservedV2装饰的嵌套对象。监听该路径的@Monitor将在以下情况触发:1、对obj整体赋值且objA变化。2、对objA整体赋值。3、objA任意@Trace属性变化。 |
513
+ | arr.1.* | arr为嵌套可观察数组。监听该路径的@Monitor将在以下情况触发:1、对arr整体赋值且下标为1的数组发生变化。2、arr下标为1的数组任意元素变化或数组长度变化。3、调用arr下标为1的数组的API。 |
514
+ 当修改嵌套对象中任意对象时,监听包含该对象的通配符路径遵循最终确定值原则,即通配符前的最后一个确定值变化时,才会触发监听回调。例如,监听上表的路径“obj.objA.*”时,”objA“为通配符前的最后一个确定值,对”obj“整体赋值,若”objA“赋值前后引用同一个对象,则不会触发回调。
515
+ 此外,使用通配符时,IMonitor的dirty数组能正常包含通配符路径,但其对应的IMonitorValue的before值与now值都将为undefined。
516
+ #### 使用通配符监听对象属性变化
517
+ 当使用通配符监听对象时,对象的任意@Trace装饰的属性变化,或者对象本身被整体赋新值时,触发@Monitor回调。
518
+ ```
519
+ import { hilog } from '@kit.PerformanceAnalysisKit';
520
+ @ObservedV2
521
+ class ClassA {
522
+ @Trace public propA: number = 8;
523
+ @Trace public propB: number = 99;
524
+ constructor(a: number, b: number) {
525
+ this.propA = a;
526
+ this.propB = b;
527
+ }
528
+ }
529
+ @Entry
530
+ @ComponentV2
531
+ struct MonitorWildcardObject {
532
+ @Local cls: ClassA = new ClassA(100, 100);
533
+ // 使能通配符
534
+ @Monitor({}, 'cls.*')
535
+ onClsChanged(m: IMonitor) {
536
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onClsChanged, dirty: ${m.dirty.toString()}`);
537
+ }
538
+ build() {
539
+ Column() {
540
+ Button(`Change propA: ${this.cls.propA}`)
541
+ .onClick(() => {
542
+ this.cls.propA += 1; // 触发onClsChanged
543
+ })
544
+ Button(`Change propB: ${this.cls.propB}`)
545
+ .onClick(() => {
546
+ this.cls.propB += 1; // 触发onClsChanged
547
+ })
548
+ Button('Assign new object')
549
+ .onClick(() => {
550
+ this.cls = new ClassA(-200, -200); // 触发onClsChanged
551
+ })
552
+ }
553
+ }
554
+ }
555
+ ```
556
+ #### 使用通配符监听嵌套对象属性变化
557
+ 观察嵌套对象属性变化的示例如下。
558
+ ```
559
+ import { hilog } from '@kit.PerformanceAnalysisKit';
560
+ @ObservedV2
561
+ class Person {
562
+ @Trace public firstName: string = 'first';
563
+ @Trace public lastName: string = 'last';
564
+ }
565
+ @ObservedV2
566
+ class Class1 {
567
+ @Trace public person: Person = new Person();
568
+ }
569
+ @ObservedV2
570
+ class Class0 {
571
+ @Trace public class1: Class1 = new Class1();
572
+ }
573
+ @Entry
574
+ @ComponentV2
575
+ struct MonitorWildcardNestedObject {
576
+ @Local class0: Class0 | number = new Class0();
577
+ // 使能通配符,监听嵌套对象
578
+ @Monitor({}, 'class0.class1.person.*')
579
+ onPersonChange(info: IMonitor) {
580
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'onPersonChange, dirty: ' + info.dirty.toString());
581
+ }
582
+ build() {
583
+ Column({ space: 5 }) {
584
+ Button('1. Class0 = new Class')
585
+ .onClick(() => {
586
+ // @Monitor回调触发
587
+ // 原因:class0, class1, person变更为新对象
588
+ this.class0 = new Class0();
589
+ })
590
+ Button('2. Class0 = new Class, keep Class1')
591
+ .onClick(() => {
592
+ // 当class0为Class0类型时,@Monitor回调不触发
593
+ // 原因:即使class0变化了,路径'class0.class1.person.*'中通配符前最后一个确定值person也没有改变
594
+ if (this.class0 instanceof Class0) {
595
+ let newClass0 = new Class0();
596
+ newClass0.class1.person = (this.class0 as Class0).class1.person;
597
+ this.class0 = newClass0;
598
+ }
599
+ })
600
+ Button('3. Class0.class1 = new Class1')
601
+ .onClick(() => {
602
+ // 当class0为Class0类型时,@Monitor回调触发
603
+ // 原因:class1、person变更为新对象
604
+ if (this.class0 instanceof Class0) {
605
+ (this.class0 as Class0).class1 = new Class1();
606
+ }
607
+ })
608
+ Button('4. Class0.class1.person = new Person')
609
+ .onClick(() => {
610
+ // 当class0为Class0类型时,@Monitor回调触发
611
+ // 原因:person变更为新对象
612
+ if (this.class0 instanceof Class0) {
613
+ (this.class0 as Class0).class1.person = new Person();
614
+ }
615
+ })
616
+ Button('5. Class0....person.last update')
617
+ .onClick(() => {
618
+ // 当class0为Class0类型时,@Monitor回调触发
619
+ // 原因:person的属性发生变化
620
+ if (this.class0 instanceof Class0) {
621
+ (this.class0 as Class0).class1.person.lastName += '+';
622
+ }
623
+ })
624
+ Button('6. Class0 toggle number <=> new Class0')
625
+ .onClick(() => {
626
+ // @Monitor回调触发
627
+ // 原因:person在可访问与不可访问之间切换
628
+ this.class0 = (typeof this.class0 === 'object') ? 500 : new Class0();
629
+ })
630
+ }
631
+ }
632
+ }
633
+ ```
634
+ 当使用配置项的@Monitor监听的变量在可访问和不可访问之间切换时,都会触发@Monitor回调。
635
+ #### 使用通配符监听数组对象的变化
636
+ 使用配置项的@Monitor可以监听到数组的API调用。任意数组的方法被调用时,@Monitor回调都会被执行,即使数组为空或并未实际修改数组的内容。API包括push、pop、shift、splice、unshift、copyWithin、fill、reverse、sort。
637
+ ```
638
+ import { hilog } from '@kit.PerformanceAnalysisKit';
639
+ @ObservedV2
640
+ class Person {
641
+ @Trace public firstName: string = 'first';
642
+ @Trace public lastName: string = 'last';
643
+ constructor(first: string = 'no first', last: string = 'no last') {
644
+ this.firstName = first;
645
+ this.lastName = last;
646
+ }
647
+ }
648
+ @ObservedV2
649
+ class ArrayOfPerson extends Array<Person> {
650
+ }
651
+ @ObservedV2
652
+ class TopArray extends Array<ArrayOfPerson> {
653
+ }
654
+ @Entry
655
+ @ComponentV2
656
+ struct MonitorWildcardArray {
657
+ @Local topArray: TopArray = this.makeNewTopArray();
658
+ // 使能通配符
659
+ @Monitor({}, 'topArray.1.*')
660
+ topArrayMonitor1Star(monitor: IMonitor) {
661
+ hilog.info(0xFF00, 'testTag', '%{public}s', `TopArray[1]: ${monitor.dirty.toString()}`);
662
+ }
663
+ // 使能通配符
664
+ @Monitor({}, 'topArray.*')
665
+ topArrayMonitorStar(monitor: IMonitor) {
666
+ hilog.info(0xFF00, 'testTag', '%{public}s', `TopArray: ${monitor.dirty.toString()}`);
667
+ }
668
+ makeNewTopArray(): TopArray {
669
+ // 初始化数组
670
+ return new TopArray(
671
+ new ArrayOfPerson(new Person('Adrian'), new Person('Andrew'), new Person('Aaliyah'), new Person('Amir'),
672
+ new Person('Angel')),
673
+ new ArrayOfPerson(new Person('Carter'), new Person('Charlie'), new Person('Cooper'), new Person('Cole'),
674
+ new Person('Callie')),
675
+ new ArrayOfPerson(new Person('Daniel'), new Person('Daisy'), new Person('Dawson'), new Person('Dana'),
676
+ new Person('Dalton'))
677
+ );
678
+ }
679
+ build() {
680
+ Column() {
681
+ // topArrayMonitor1Star与topArrayMonitorStar回调均触发
682
+ Button('topArray = new TopArray')
683
+ .onClick(() => {
684
+ this.topArray = this.makeNewTopArray();
685
+ })
686
+ // 当topArray[1][0]存在时,topArrayMonitor1Star回调触发,topArrayMonitorStar回调不触发
687
+ Button('topArray[1][0] = new Person')
688
+ .onClick(() => {
689
+ if (this.topArray.length > 1 && this.topArray[1].length > 0) {
690
+ this.topArray[1][0] = new Person();
691
+ }
692
+ })
693
+ // 当topArray[0][1]存在时,topArrayMonitor1Star与topArrayMonitorStar回调均不触发
694
+ Button('topArray[0][1] = new Person')
695
+ .onClick(() => {
696
+ if (this.topArray.length > 0 && this.topArray[0].length > 1) {
697
+ this.topArray[0][1] = new Person();
698
+ }
699
+ })
700
+ // 当topArray[1]存在时,topArrayMonitor1Star回调触发,topArrayMonitorStar回调不触发
701
+ Button('topArray[1].push')
702
+ .onClick(() => {
703
+ if (this.topArray.length > 1 && this.topArray[1] instanceof ArrayOfPerson) {
704
+ this.topArray[1].push(new Person());
705
+ }
706
+ })
707
+ // 当topArray的length大于2时,topArrayMonitor1Star与topArrayMonitorStar回调均触发
708
+ Button('topArray.shift (length>2)')
709
+ .onClick(() => {
710
+ if (this.topArray.length > 2) {
711
+ this.topArray.shift();
712
+ }
713
+ })
714
+ // 当topArray[0]存在时,topArrayMonitor1Star回调不触发,topArrayMonitorStar回调触发
715
+ Button('topArray[0] = new ArrayOfPerson')
716
+ .onClick(() => {
717
+ if (this.topArray.length > 0) {
718
+ this.topArray[0] = new ArrayOfPerson(new Person(), new Person());
719
+ }
720
+ })
721
+ // 当topArray[1][0]存在时,topArrayMonitor1Star与topArrayMonitorStar回调均不触发
722
+ Button('topArray[1][0].last update')
723
+ .onClick(() => {
724
+ if (this.topArray.length > 1 && this.topArray[1].length > 0 && this.topArray[1][0] instanceof Person) {
725
+ this.topArray[1][0].lastName += '~';
726
+ }
727
+ })
728
+ // topArrayMonitor1Star回调不触发,topArrayMonitorStar回调触发
729
+ Button('topArray = new TopArray, keep [1]')
730
+ .onClick(() => {
731
+ let newTop = this.makeNewTopArray();
732
+ newTop[1] = this.topArray[1]; // topArray.1未改变,路径'topArray.1.*'中通配符前最后一个确定值未改变
733
+ this.topArray = newTop;
734
+ })
735
+ // topArrayMonitor1Star回调不触发,topArrayMonitorStar回调触发
736
+ Button('topArray.push')
737
+ .onClick(() => {
738
+ this.topArray.push(new ArrayOfPerson(new Person(), new Person()));
739
+ })
740
+ }
741
+ }
742
+ }
743
+ ```
744
+ #### 使用通配符监听Date对象的变化
745
+ 使用通配符可以监听Date对象的API调用。
746
+ ```
747
+ @Monitor({}, 'dateInstance.*')
748
+ onDateChange(m: IMonitor) {
749
+ }
750
+ ```
751
+ @Monitor会在以下情况回调:
752
+ - dateInstance被赋新值。
753
+ - 调用Date的任意API,包括setFullYear、setMonth、setDate、setHours、setMinutes、setSeconds、setMilliseconds、setTime、setUTCFullYear、setUTCMonth、setUTCDate、setUTCHours、setUTCMinutes、setUTCSeconds、setUTCMilliseconds。即使这些API未实际对Date的值产生更改,@Monitor回调也会触发。
754
+ 使用通配符监听Date对象的示例如下。
755
+ ```
756
+ import { hilog } from '@kit.PerformanceAnalysisKit';
757
+ @Entry
758
+ @ComponentV2
759
+ struct MonitorWildcardDate {
760
+ @Local date: Date = new Date();
761
+ // 使能通配符
762
+ @Monitor({}, 'date.*')
763
+ onDateChanged(m: IMonitor) {
764
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onDateChanged, dirty: ${m.dirty.toString()}`);
765
+ }
766
+ build() {
767
+ Column({ space: 5 }) {
768
+ // API调用触发onDateChanged
769
+ Button(`date.setMilliseconds(1000)`)
770
+ .onClick(() => {
771
+ this.date.setMilliseconds(1000);
772
+ })
773
+ // API调用触发onDateChanged
774
+ Button(`date.setTime(1000)`)
775
+ .onClick(() => {
776
+ this.date.setTime(1000000);
777
+ })
778
+ // API调用触发onDateChanged
779
+ Button(`Assign new Date`)
780
+ .onClick(() => {
781
+ this.date = new Date();
782
+ })
783
+ // 整体赋相同值,不触发onDateChanged
784
+ Button(`Re-assign the same Date`)
785
+ .onClick(() => {
786
+ let sameDate = this.date;
787
+ this.date = sameDate;
788
+ })
789
+ }
790
+ }
791
+ }
792
+ ```
793
+ #### 使用通配符监听Map对象的变化
794
+ 使用通配符可以监听Map对象的API调用。
795
+ ```
796
+ @Monitor({}, 'mapInstance.*')
797
+ onMapChange(m: IMonitor) {
798
+ }
799
+ ```
800
+ @Monitor会在以下情况回调:
801
+ - mapInstance被赋新值。
802
+ - 调用Map的API,例如set、delete、clear时触发。与Array、Date不同的是,只有当变化真的发生时,回调才会触发。这意味着,当对空Map调用clear,对不存在的Map键值调用delete,以及不实际改变值的set调用都不会触发@Monitor回调。
803
+ 与Array不同,@Monitor无法对Map的某一个key做监听。
804
+ 使用通配符监听Map对象的示例如下。
805
+ ```
806
+ import { hilog } from '@kit.PerformanceAnalysisKit';
807
+ @Entry
808
+ @ComponentV2
809
+ struct MonitorWildcardMap {
810
+ @Local map: Map<string, string> = new Map<string, string>();
811
+ cnt: number = 0;
812
+ @Monitor({ enableWildcard: false }, 'map.size')
813
+ onMapSizeChanged(m: IMonitor) {
814
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onMapSizeChanged, size dirty: ${m.dirty.toString()}`);
815
+ }
816
+ // 使能通配符
817
+ @Monitor({}, 'map.*')
818
+ onMapChanged(m: IMonitor) {
819
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onMapChanged, dirty: ${m.dirty.toString()}`);
820
+ }
821
+ build() {
822
+ Column({ space: 5 }) {
823
+ Text(`map.size: ${this.map.size}`)
824
+ Text(`map.get('one'): ${this.map.get('one')}`)
825
+ // 在首次点击时,onMapSizeChanged、onMapChanged回调都触发
826
+ Button(`Init, map.set('one', 'A'), map.set('two', 'B')`)
827
+ .onClick(() => {
828
+ this.map.set('one', 'A');
829
+ this.map.set('two', 'B');
830
+ })
831
+ // onMapSizeChanged、onMapChanged回调都触发
832
+ Button(`Add new, map.set('three' + this.cnt, 'C')`)
833
+ .onClick(() => {
834
+ this.cnt++;
835
+ this.map.set('three' + this.cnt, 'C')
836
+ })
837
+ // 当'one'不存在时,onMapSizeChanged、onMapChanged回调都不触发
838
+ // 当'one'存在时,onMapSizeChanged、onMapChanged回调都触发
839
+ Button(`Delete from map: map.delete('one')`)
840
+ .onClick(() => {
841
+ this.map.delete('one')
842
+ })
843
+ // 当map不为空时,onMapSizeChanged、onMapChanged回调都触发
844
+ // 当map为空时,onMapSizeChanged、onMapChanged回调都不触发
845
+ Button(`Clear map`)
846
+ .onClick(() => {
847
+ this.map.clear();
848
+ })
849
+ // 在首次点击且假设存在('one' -> 'A')时,仅onMapChanged回调触发
850
+ // 若已经设置过('one' -> 'TWO'),则onMapSizeChanged、onMapChanged回调都不触发
851
+ Button(`Update one to 'TWO' - map.set('one', 'TWO')`)
852
+ .onClick(() => {
853
+ this.map.set('one', 'TWO');
854
+ })
855
+ // 当Map不存在'one'时,onMapSizeChanged、onMapChanged回调都触发
856
+ // 当Map存在'one'时,onMapSizeChanged、onMapChanged回调都不会触发
857
+ Button(`Update one to the same - map.set('one', sameval)`)
858
+ .onClick(() => {
859
+ const sameval = this.map.get('one') ?? 'one';
860
+ this.map.set('one', sameval);
861
+ })
862
+ // 当Map不存在'one'时,onMapSizeChanged、onMapChanged回调都触发
863
+ // 当Map存在'one'时,仅onMapChanged回调触发
864
+ Button(`Update one to new value - map.set('one', newval)`)
865
+ .onClick(() => {
866
+ let newval = 'x' + (++this.cnt);
867
+ this.map.set('one', newval);
868
+ })
869
+ // 当map为空时,仅onMapChanged回调触发
870
+ // 当map不为空时,onMapChanged、onMapSizeChanged回调都触发
871
+ Button(`new map`)
872
+ .onClick(() => {
873
+ this.map = new Map();
874
+ })
875
+ }
876
+ .border({ style: BorderStyle.Solid, width: 2, color: Color.Green })
877
+ }
878
+ }
879
+ ```
880
+ #### 使用通配符监听Set对象的变化
881
+ 使用通配符可以监听Set对象的API调用。
882
+ ```
883
+ @Monitor({}, 'setInstance.*')
884
+ onSetChange(m: IMonitor) {
885
+ }
886
+ ```
887
+ @Monitor会在以下情况回调:
888
+ - setInstance被赋新值。
889
+ - 调用Set的API,例如add、delete、clear时触发。与Array、Date不同的是,只有当变化真的发生时,回调才会触发。这意味着,当对空Set调用clear,对不存在的Set元素调用delete,以及不实际新增元素的add调用都不会触发@Monitor回调。
890
+ 与Array不同,@Monitor无法对Set的某一个key做监听。
891
+ 使用通配符监听Set对象的示例如下。
892
+ ```
893
+ import { hilog } from '@kit.PerformanceAnalysisKit';
894
+ @Entry
895
+ @ComponentV2
896
+ struct MonitorWildcardSet {
897
+ @Local set: Set<string> = new Set<string>();
898
+ cnt: number = 0;
899
+ // 使能通配符
900
+ @Monitor({}, 'set.*')
901
+ onSetChanged(m: IMonitor) {
902
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onSetChanged, dirty: ${m.dirty.toString()}`);
903
+ }
904
+ @Monitor({ enableWildcard: false }, 'set.size')
905
+ onSetSizeChanged(m: IMonitor) {
906
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onSetSizeChanged, size dirty: ${m.dirty.toString()}`);
907
+ }
908
+ aboutToAppear(): void {
909
+ this.set.add('one');
910
+ this.set.add('two');
911
+ }
912
+ build() {
913
+ Column({ space: 5 }) {
914
+ // onSetChanged、onSetSizeChanged回调都触发
915
+ Button(`Add three<Num> to the set`)
916
+ .onClick(() => {
917
+ this.cnt++;
918
+ this.set.add('three' + this.cnt);
919
+ })
920
+ // 当元素不存在时,onSetChanged、onSetSizeChanged回调都不触发
921
+ // 当元素存在时,onSetChanged、onSetSizeChanged回调都触发
922
+ Button(`Delete 'three<Num>' from the set - set.delete(...)`)
923
+ .onClick(() => {
924
+ this.set.delete('three' + this.cnt);
925
+ })
926
+ // 当set不为空时,onSetChanged、onSetSizeChanged回调都触发
927
+ // 当set为空时,onSetChanged、onSetSizeChanged回调都不触发
928
+ Button(`Clear the set - set.clear()`)
929
+ .onClick(() => {
930
+ this.set.clear();
931
+ })
932
+ // 当set不为空时,onSetChanged、onSetSizeChanged回调都触发
933
+ // 当set为空时,仅onSetChanged回调触发
934
+ Button(`Assign new set`)
935
+ .onClick(() => {
936
+ this.set = new Set();
937
+ })
938
+ // 当set不包含'one'时,onSetChanged、onSetSizeChanged回调都触发
939
+ // 当set包含'one'时,onSetChanged、onSetSizeChanged回调都不触发
940
+ Button(`Add 'one' to the set`)
941
+ .onClick(() => {
942
+ this.set.add('one');
943
+ })
944
+ }
945
+ }
946
+ }
947
+ ```
948
+ #### 限制条件
949
+ 使用@Monitor需要注意如下限制条件:
950
+ -
951
+ 不建议在一个类中对同一个属性进行多次@Monitor的监听。当一个类中存在对一个属性的多次监听时,只有最后一个定义的监听方法会生效。
952
+ ```
953
+ import { hilog } from '@kit.PerformanceAnalysisKit';
954
+ @ObservedV2
955
+ class Info {
956
+ @Trace public name: string = 'Tom';
957
+ @Monitor('name')
958
+ onNameChange(monitor: IMonitor) {
959
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onNameChange`);
960
+ }
961
+ @Monitor('name')
962
+ onNameChangeDuplicate(monitor: IMonitor) {
963
+ hilog.info(0xFF00, 'testTag', '%{public}s', `onNameChangeDuplicate`);
964
+ }
965
+ }
966
+ @Entry
967
+ @ComponentV2
968
+ struct Index {
969
+ info: Info = new Info();
970
+ build() {
971
+ Column() {
972
+ Button('change name')
973
+ .onClick(() => {
974
+ this.info.name = 'Jack'; // 仅会触发onNameChangeDuplicate方法
975
+ })
976
+ }
977
+ }
978
+ }
979
+ ```
980
+ -
981
+ 当@Monitor传入多个路径参数时,以参数的全拼接结果判断是否重复监听。全拼接时会在参数间加空格,以区分不同参数。例如,'ab', 'c'的全拼接结果为'ab c','a', 'bc'的全拼接结果为'a bc',二者全拼接不相等。以下示例中,Monitor 1、Monitor 2与Monitor 3都监听了name属性的变化。由于Monitor 2与Monitor 3的入参全拼接相等(都为'name position'),因此Monitor 2不生效,仅Monitor 3生效。当name属性变化时,将同时触发onNameAgeChange与onNamePositionChangeDuplicate方法。但请注意,Monitor 2与Monitor 3的写法仍然被视作在一个类中对同一个属性进行多次@Monitor的监听,这是不建议的。
982
+ ```
983
+ import { hilog } from '@kit.PerformanceAnalysisKit';
984
+ @ObservedV2
985
+ class Info {
986
+ @Trace public name: string = 'Tom';
987
+ @Trace public age: number = 25;
988
+ @Trace public position: string = 'North';
989
+ @Monitor('name', 'age') // Monitor 1
990
+ onNameAgeChange(monitor: IMonitor) {
991
+ monitor.dirty.forEach((path: string) => {
992
+ hilog.info(0xFF00, 'testTag', '%{public}s',
993
+ `onNameAgeChange path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
994
+ });
995
+ }
996
+ @Monitor('name', 'position') // Monitor 2
997
+ onNamePositionChange(monitor: IMonitor) {
998
+ monitor.dirty.forEach((path: string) => {
999
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1000
+ `onNamePositionChange path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1001
+ });
1002
+ }
1003
+ // 重复监听name、position,仅最后定义的生效
1004
+ @Monitor('name', 'position') // Monitor3
1005
+ onNamePositionChangeDuplicate(monitor: IMonitor) {
1006
+ monitor.dirty.forEach((path: string) => {
1007
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1008
+ `onNamePositionChangeDuplicate path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1009
+ });
1010
+ }
1011
+ }
1012
+ @Entry
1013
+ @ComponentV2
1014
+ struct Index {
1015
+ info: Info = new Info();
1016
+ build() {
1017
+ Column() {
1018
+ Button('change name')
1019
+ .onClick(() => {
1020
+ this.info.name = 'Jack'; // 同时触发onNameAgeChange与onNamePositionChangeDuplicate方法
1021
+ })
1022
+ }
1023
+ }
1024
+ }
1025
+ ```
1026
+ -
1027
+ @Monitor的参数需要为监听属性名的字符串,仅可以使用字符串字面量、const常量、enum枚举值作为参数。如果使用变量作为参数,仅会监听@Monitor初始化时,变量值所对应的属性。当更改变量时,@Monitor无法实时改变监听的属性,即@Monitor监听的目标属性从初始化时便已经确定,无法动态更改。不建议开发者使用变量作为@Monitor的参数进行初始化。
1028
+ ```
1029
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1030
+ const t2: string = 't2'; // const常量
1031
+ enum ENUM {
1032
+ T3 = 't3' // enum枚举值
1033
+ };
1034
+ let t4: string = 't4'; // 变量
1035
+ @ObservedV2
1036
+ class Info {
1037
+ @Trace public t1: number = 0;
1038
+ @Trace public t2: number = 0;
1039
+ @Trace public t3: number = 0;
1040
+ @Trace public t4: number = 0;
1041
+ @Trace public t5: number = 0;
1042
+ // 字符串字面量
1043
+ @Monitor('t1')
1044
+ onT1Change(monitor: IMonitor) {
1045
+ hilog.info(0xFF00, 'testTag', '%{public}s', `t1 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1046
+ }
1047
+ @Monitor(t2)
1048
+ onT2Change(monitor: IMonitor) {
1049
+ hilog.info(0xFF00, 'testTag', '%{public}s', `t2 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1050
+ }
1051
+ @Monitor(ENUM.T3)
1052
+ onT3Change(monitor: IMonitor) {
1053
+ hilog.info(0xFF00, 'testTag', '%{public}s', `t3 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1054
+ }
1055
+ @Monitor(t4)
1056
+ onT4Change(monitor: IMonitor) {
1057
+ hilog.info(0xFF00, 'testTag', '%{public}s', `t4 change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1058
+ }
1059
+ }
1060
+ @Entry
1061
+ @ComponentV2
1062
+ struct Index {
1063
+ info: Info = new Info();
1064
+ build() {
1065
+ Column() {
1066
+ Button('Change t1')
1067
+ .onClick(() => {
1068
+ this.info.t1++; // 能够触发onT1Change方法
1069
+ })
1070
+ Button('Change t2')
1071
+ .onClick(() => {
1072
+ this.info.t2++; // 能够触发onT2Change方法
1073
+ })
1074
+ Button('Change t3')
1075
+ .onClick(() => {
1076
+ this.info.t3++; // 能够触发onT3Change方法
1077
+ })
1078
+ Button('Change t4')
1079
+ .onClick(() => {
1080
+ this.info.t4++; // 能够触发onT4Change方法
1081
+ })
1082
+ Button('Change var t4 to t5')
1083
+ .onClick(() => {
1084
+ t4 = 't5'; // 更改变量值为't5'
1085
+ })
1086
+ Button('Change t5')
1087
+ .onClick(() => {
1088
+ this.info.t5++; // onT4Change仍监听t4,不会触发
1089
+ })
1090
+ Button('Change t4 again')
1091
+ .onClick(() => {
1092
+ this.info.t4++; // 能够触发onT4Change方法
1093
+ })
1094
+ }
1095
+ }
1096
+ }
1097
+ ```
1098
+ -
1099
+ 建议开发者避免在@Monitor中再次更改被监听的属性,这会导致无限循环。
1100
+ ```
1101
+ @ObservedV2
1102
+ class Info {
1103
+ @Trace count: number = 0;
1104
+ @Monitor('count')
1105
+ onCountChange(monitor: IMonitor) {
1106
+ this.count++; // 应避免这种写法,会导致无限循环
1107
+ }
1108
+ }
1109
+ ```
1110
+ #### @Monitor与@Watch对比
1111
+ @Monitor与@Watch的用法、功能对比如下:
1112
+ | 用法 | @Watch | @Monitor |
1113
+ | --- | --- | --- |
1114
+ | 参数 | 回调方法名。 | 监听状态变量名、属性名。 |
1115
+ | 监听目标数 | 只能监听单个状态变量。 | 能同时监听多个状态变量。 |
1116
+ | 监听能力 | 跟随状态变量观察能力(一层)。 | 跟随状态变量观察能力(深层)。 |
1117
+ | 能否获取变化前的值 | 不能获取变化前的值。 | 能获取变化前的值。 |
1118
+ | 监听条件 | 监听对象为状态变量。 | 监听对象为状态变量或为@Trace装饰的类成员属性。 |
1119
+ | 使用限制 | 仅能在@Component装饰的自定义组件中使用。 | 能在@ComponentV2装饰的自定义组件中使用,也能在@ObservedV2装饰的类中使用。 |
1120
+ #### 使用场景
1121
+ #### 监听深层属性变化
1122
+ @Monitor可以监听深层属性的变化,并能够根据更改前后的值做分类处理。
1123
+ 下面的示例中监听了属性value的变化,并根据变化的幅度改变Text组件显示的样式。
1124
+ ```
1125
+ @ObservedV2
1126
+ class Info {
1127
+ @Trace public value: number = 50;
1128
+ }
1129
+ @ObservedV2
1130
+ class UIStyle {
1131
+ public info: Info = new Info();
1132
+ @Trace public color: Color = Color.Black;
1133
+ @Trace public fontSize: number = 45;
1134
+ @Monitor('info.value')
1135
+ onValueChange(monitor: IMonitor) {
1136
+ let lastValue: number = monitor.value()?.before as number;
1137
+ let curValue: number = monitor.value()?.now as number;
1138
+ if (lastValue != 0) {
1139
+ let diffPercent: number = (curValue - lastValue) / lastValue;
1140
+ // 通过info.value变化的幅度,改变Text组件显示的样式
1141
+ if (diffPercent > 0.1) {
1142
+ this.color = Color.Red;
1143
+ this.fontSize = 50;
1144
+ } else if (diffPercent < -0.1) {
1145
+ this.color = Color.Green;
1146
+ this.fontSize = 40;
1147
+ } else {
1148
+ this.color = Color.Black;
1149
+ this.fontSize = 45;
1150
+ }
1151
+ }
1152
+ }
1153
+ }
1154
+ @Entry
1155
+ @ComponentV2
1156
+ struct Index {
1157
+ textStyle: UIStyle = new UIStyle();
1158
+ build() {
1159
+ Column() {
1160
+ Text(`Important Value: ${this.textStyle.info.value}`)
1161
+ .fontColor(this.textStyle.color)
1162
+ .fontSize(this.textStyle.fontSize)
1163
+ Button('change!')
1164
+ .onClick(() => {
1165
+ this.textStyle.info.value = Math.floor(Math.random() * 100) + 1;
1166
+ })
1167
+ }
1168
+ }
1169
+ }
1170
+ ```
1171
+ #### 常见问题
1172
+ #### 自定义组件中@Monitor对变量监听的生效及失效时间
1173
+ 当@Monitor定义在@ComponentV2装饰的自定义组件中时,@Monitor会在状态变量初始化完成之后生效,并在组件销毁时失效。
1174
+ ```
1175
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1176
+ @ObservedV2
1177
+ class Info {
1178
+ @Trace public message: string = 'not initialized';
1179
+ constructor() {
1180
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'in constructor message change to initialized');
1181
+ // 此时@Monitor还未初始化成功,因此不会监听到message的变化
1182
+ this.message = 'initialized';
1183
+ }
1184
+ }
1185
+ @ComponentV2
1186
+ struct Child {
1187
+ @Param info: Info = new Info();
1188
+ @Monitor('info.message')
1189
+ onMessageChange(monitor: IMonitor) {
1190
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1191
+ `Child message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1192
+ }
1193
+ aboutToAppear(): void {
1194
+ this.info.message = 'Child aboutToAppear';
1195
+ }
1196
+ aboutToDisappear(): void {
1197
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'Child aboutToDisappear');
1198
+ this.info.message = 'Child aboutToDisappear';
1199
+ }
1200
+ build() {
1201
+ Column() {
1202
+ Text('Child')
1203
+ Button('change message in Child')
1204
+ .onClick(() => {
1205
+ this.info.message = 'Child click to change Message';
1206
+ })
1207
+ }
1208
+ .borderColor(Color.Red)
1209
+ .borderWidth(2)
1210
+ }
1211
+ }
1212
+ @Entry
1213
+ @ComponentV2
1214
+ struct Index {
1215
+ @Local info: Info = new Info();
1216
+ @Local flag: boolean = false;
1217
+ @Monitor('info.message')
1218
+ onMessageChange(monitor: IMonitor) {
1219
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1220
+ `Index message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1221
+ }
1222
+ build() {
1223
+ Column() {
1224
+ Button('show/hide Child')
1225
+ .onClick(() => {
1226
+ this.flag = !this.flag
1227
+ })
1228
+ Button('change message in Index')
1229
+ .onClick(() => {
1230
+ this.info.message = 'Index click to change Message';
1231
+ })
1232
+ if (this.flag) {
1233
+ Child({ info: this.info })
1234
+ }
1235
+ }
1236
+ }
1237
+ }
1238
+ ```
1239
+ 在上面的例子中,可以通过创建和销毁Child组件来观察定义在自定义组件中的@Monitor的生效和失效时机。推荐按如下顺序进行操作:
1240
+ - 当Index组件创建Info类实例时,日志输出in constructor message change to initialized。此时Index组件的@Monitor还未初始化成功,因此不会监听到message的变化。
1241
+ - 当Index组件创建完成,页面加载完成后,点击按钮“change message in Index”,此时Index组件中的@Monitor能够监听到变化,日志输出Index message change from initialized to Index click to change Message。
1242
+ - 点击按钮“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。
1243
+ - 点击按钮“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。
1244
+ - 点击按钮”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。
1245
+ - 点击按钮“change message in Index”,改变message。此时Child组件已销毁,其注册的@Monitor监听也被解注册,仅有Index组件的@Monitor能够监听到变化,日志输出Index message change from Child aboutToDisappear to Index click to change Message。
1246
+ 这表明Child组件中定义的@Monitor监听随着Child组件的创建初始化生效,随着Child组件的销毁失效。
1247
+ #### 类中@Monitor对变量监听的生效及失效时间
1248
+ 当@Monitor定义在@ObservedV2装饰的类中时,@Monitor会在类的实例创建完成后生效,在类的实例销毁时失效。
1249
+ ```
1250
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1251
+ @ObservedV2
1252
+ class Info {
1253
+ @Trace public message: string = 'not initialized';
1254
+ constructor() {
1255
+ // 此时@Monitor还未生效,因此不会监听到message的变化
1256
+ this.message = 'initialized';
1257
+ }
1258
+ @Monitor('message')
1259
+ onMessageChange(monitor: IMonitor) {
1260
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1261
+ `message change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1262
+ }
1263
+ }
1264
+ @Entry
1265
+ @ComponentV2
1266
+ struct Index {
1267
+ info: Info = new Info();
1268
+ aboutToAppear(): void {
1269
+ this.info.message = 'Index aboutToAppear';
1270
+ }
1271
+ build() {
1272
+ Column() {
1273
+ Button('change message')
1274
+ .onClick(() => {
1275
+ this.info.message = 'Index click to change message';
1276
+ })
1277
+ }
1278
+ }
1279
+ }
1280
+ ```
1281
+ 上面的例子中,@Monitor会在info创建完成后生效,这个时机晚于类的constructor,早于自定义组件的aboutToAppear。当界面加载完成后,点击“change message”,修改message变量。此时日志输出信息如下:
1282
+ ```
1283
+ message change from initialized to Index aboutToAppear
1284
+ message change from Index aboutToAppear to Index click to change message
1285
+ ```
1286
+ 类中定义的@Monitor随着类的销毁失效。而由于类的实际销毁释放依赖于垃圾回收机制,因此会出现即使所在自定义组件已经销毁,类却还未及时销毁,导致类中定义的@Monitor仍在监听变化的情况。
1287
+ ```
1288
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1289
+ @ObservedV2
1290
+ class InfoWrapper {
1291
+ public info?: Info;
1292
+ constructor(info: Info) {
1293
+ this.info = info;
1294
+ }
1295
+ @Monitor('info.age')
1296
+ onInfoAgeChange(monitor: IMonitor) {
1297
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1298
+ `age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1299
+ }
1300
+ }
1301
+ @ObservedV2
1302
+ class Info {
1303
+ @Trace public age: number;
1304
+ constructor(age: number) {
1305
+ this.age = age;
1306
+ }
1307
+ }
1308
+ @ComponentV2
1309
+ struct Child {
1310
+ @Param @Require infoWrapper: InfoWrapper;
1311
+ aboutToDisappear(): void {
1312
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'Child aboutToDisappear', this.infoWrapper.info?.age);
1313
+ }
1314
+ build() {
1315
+ Column() {
1316
+ Text(`${this.infoWrapper.info?.age}`)
1317
+ }
1318
+ }
1319
+ }
1320
+ @Entry
1321
+ @ComponentV2
1322
+ struct Index {
1323
+ dataArray: Info[] = [];
1324
+ @Local showFlag: boolean = true;
1325
+ aboutToAppear(): void {
1326
+ for (let i = 0; i < 5; i++) {
1327
+ this.dataArray.push(new Info(i));
1328
+ }
1329
+ }
1330
+ build() {
1331
+ Column() {
1332
+ // 点击Button切换showFlag,触发Child组件的创建/销毁
1333
+ Button('change showFlag')
1334
+ .onClick(() => {
1335
+ this.showFlag = !this.showFlag;
1336
+ })
1337
+ Button('change number')
1338
+ .onClick(() => {
1339
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'click to change age');
1340
+ this.dataArray.forEach((info: Info) => {
1341
+ info.age += 100;
1342
+ });
1343
+ })
1344
+ if (this.showFlag) {
1345
+ Column() {
1346
+ Text('Children')
1347
+ ForEach(this.dataArray, (info: Info) => {
1348
+ Child({ infoWrapper: new InfoWrapper(info) })
1349
+ })
1350
+ }
1351
+ .borderColor(Color.Red)
1352
+ .borderWidth(2)
1353
+ }
1354
+ }
1355
+ }
1356
+ }
1357
+ ```
1358
+ 在上面的例子中,当点击“change showFlag”切换if组件的条件时,Child组件会被销毁。此时,点击“change number”修改age的值时,可以通过日志观察到InfoWrapper中定义的@Monitor回调仍然被触发了。这是因为此时自定义组件Child虽然执行了aboutToDisappear,但是其成员变量infoWrapper还没有被立刻回收,当变量发生变化时,依然能够调用到infoWrapper中定义的onInfoAgeChange方法,所以从现象上看@Monitor回调仍会被触发。
1359
+ 借助垃圾回收机制去取消@Monitor的监听是不稳定的,开发者可以采用以下两种方式去管理@Monitor的失效时间:
1360
+ 1、将@Monitor定义在自定义组件中。由于自定义组件在销毁时,状态管理框架会手动取消@Monitor的监听,因此在自定义组件调用完aboutToDisappear,尽管自定义组件的数据不一定已经被释放,但@Monitor回调已不会再被触发。
1361
+ ```
1362
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1363
+ @ObservedV2
1364
+ class InfoWrapper {
1365
+ public info?: Info;
1366
+ constructor(info: Info) {
1367
+ this.info = info;
1368
+ }
1369
+ }
1370
+ @ObservedV2
1371
+ class Info {
1372
+ @Trace public age: number;
1373
+ constructor(age: number) {
1374
+ this.age = age;
1375
+ }
1376
+ }
1377
+ @ComponentV2
1378
+ struct Child {
1379
+ @Param @Require infoWrapper: InfoWrapper;
1380
+ @Monitor('infoWrapper.info.age')
1381
+ onInfoAgeChange(monitor: IMonitor) {
1382
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1383
+ `age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1384
+ }
1385
+ aboutToDisappear(): void {
1386
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'Child aboutToDisappear', this.infoWrapper.info?.age);
1387
+ }
1388
+ build() {
1389
+ Column() {
1390
+ Text(`${this.infoWrapper.info?.age}`)
1391
+ }
1392
+ }
1393
+ }
1394
+ @Entry
1395
+ @ComponentV2
1396
+ struct Index {
1397
+ dataArray: Info[] = [];
1398
+ @Local showFlag: boolean = true;
1399
+ aboutToAppear(): void {
1400
+ for (let i = 0; i < 5; i++) {
1401
+ this.dataArray.push(new Info(i));
1402
+ }
1403
+ }
1404
+ build() {
1405
+ Column() {
1406
+ // 点击Button切换showFlag,触发Child组件的创建/销毁
1407
+ Button('change showFlag')
1408
+ .onClick(() => {
1409
+ this.showFlag = !this.showFlag;
1410
+ })
1411
+ Button('change number')
1412
+ .onClick(() => {
1413
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'click to change age');
1414
+ this.dataArray.forEach((info: Info) => {
1415
+ info.age += 100;
1416
+ })
1417
+ })
1418
+ if (this.showFlag) {
1419
+ Column() {
1420
+ Text('Children')
1421
+ ForEach(this.dataArray, (info: Info) => {
1422
+ Child({ infoWrapper: new InfoWrapper(info) })
1423
+ })
1424
+ }
1425
+ .borderColor(Color.Red)
1426
+ .borderWidth(2)
1427
+ }
1428
+ }
1429
+ }
1430
+ }
1431
+ ```
1432
+ 2、主动置空监听的对象。当自定义组件即将销毁时,主动置空@Monitor的监听目标,这样@Monitor无法再监听原监听目标的变化,达到取消@Monitor监听的效果。
1433
+ ```
1434
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1435
+ @ObservedV2
1436
+ class InfoWrapper {
1437
+ public info?: Info;
1438
+ constructor(info: Info) {
1439
+ this.info = info;
1440
+ }
1441
+ @Monitor('info.age')
1442
+ onInfoAgeChange(monitor: IMonitor) {
1443
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1444
+ `age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1445
+ }
1446
+ }
1447
+ @ObservedV2
1448
+ class Info {
1449
+ @Trace public age: number;
1450
+ constructor(age: number) {
1451
+ this.age = age;
1452
+ }
1453
+ }
1454
+ @ComponentV2
1455
+ struct Child {
1456
+ @Param @Require infoWrapper: InfoWrapper;
1457
+ aboutToDisappear(): void {
1458
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'Child aboutToDisappear', this.infoWrapper.info?.age);
1459
+ this.infoWrapper.info = undefined; // 使InfoWrapper对info.age的监听失效
1460
+ }
1461
+ build() {
1462
+ Column() {
1463
+ Text(`${this.infoWrapper.info?.age}`)
1464
+ }
1465
+ }
1466
+ }
1467
+ @Entry
1468
+ @ComponentV2
1469
+ struct Index {
1470
+ dataArray: Info[] = [];
1471
+ @Local showFlag: boolean = true;
1472
+ aboutToAppear(): void {
1473
+ for (let i = 0; i < 5; i++) {
1474
+ this.dataArray.push(new Info(i));
1475
+ }
1476
+ }
1477
+ build() {
1478
+ Column() {
1479
+ Button('change showFlag')
1480
+ .onClick(() => {
1481
+ this.showFlag = !this.showFlag;
1482
+ })
1483
+ Button('change number')
1484
+ .onClick(() => {
1485
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'click to change age');
1486
+ this.dataArray.forEach((info: Info) => {
1487
+ info.age += 100;
1488
+ })
1489
+ })
1490
+ if (this.showFlag) {
1491
+ Column() {
1492
+ Text('Children')
1493
+ ForEach(this.dataArray, (info: Info) => {
1494
+ Child({ infoWrapper: new InfoWrapper(info) })
1495
+ })
1496
+ }
1497
+ .borderColor(Color.Red)
1498
+ .borderWidth(2)
1499
+ }
1500
+ }
1501
+ }
1502
+ }
1503
+ ```
1504
+ #### 正确设置@Monitor入参
1505
+ 从API version 23起,增加了对@Monitor入参的编译时校验。当@Monitor的入参不符合监听条件时(如传入非状态变量、不存在的变量等),将会有编辑、编译告警,但@Monitor回调仍会被触发。开发者应正确传入@Monitor入参,避免监听非状态变量,防止功能异常或行为与预期不符。
1506
+ 【反例1】
1507
+ ```
1508
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1509
+ @ObservedV2
1510
+ class Info {
1511
+ public name: string = 'John';
1512
+ @Trace public age: number = 24;
1513
+ // 同时监听状态变量age和非状态变量name
1514
+ // 此时会编辑、编译告警,提示`The '@Monitor' decorator needs to monitor the state variables that exist.`
1515
+ @Monitor('age', 'name')
1516
+ onPropertyChange(monitor: IMonitor) {
1517
+ monitor.dirty.forEach((path: string) => {
1518
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1519
+ `property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1520
+ })
1521
+ }
1522
+ }
1523
+ @Entry
1524
+ @ComponentV2
1525
+ struct Index {
1526
+ info: Info = new Info();
1527
+ build() {
1528
+ Column() {
1529
+ Button('change age&name')
1530
+ .onClick(() => {
1531
+ this.info.age = 25; // 同时改变状态变量age和非状态变量name
1532
+ this.info.name = 'Johny';
1533
+ })
1534
+ }
1535
+ }
1536
+ }
1537
+ ```
1538
+ 上面的代码中,当点击按钮同时更改状态变量age和非状态变量name时,会输出以下日志:
1539
+ ```
1540
+ property path:age change from 24 to 25
1541
+ property path:name change from John to Johny
1542
+ ```
1543
+ 实际上name属性本身并不是可被观测的变量,不应被加入到@Monitor的入参当中。建议开发者去除对name属性的监听或者给name加上@Trace装饰成为状态变量。
1544
+ 【正例1】
1545
+ ```
1546
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1547
+ @ObservedV2
1548
+ class Info {
1549
+ public name: string = 'John';
1550
+ @Trace public age: number = 24;
1551
+ // 仅监听状态变量age
1552
+ @Monitor('age')
1553
+ onPropertyChange(monitor: IMonitor) {
1554
+ monitor.dirty.forEach((path: string) => {
1555
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1556
+ `property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
1557
+ })
1558
+ }
1559
+ }
1560
+ @Entry
1561
+ @ComponentV2
1562
+ struct Index {
1563
+ info: Info = new Info();
1564
+ build() {
1565
+ Column() {
1566
+ Button('change age&name')
1567
+ .onClick(() => {
1568
+ this.info.age = 25; // 状态变量age改变
1569
+ this.info.name = 'Johny';
1570
+ })
1571
+ }
1572
+ }
1573
+ }
1574
+ ```
1575
+ 【反例2】
1576
+ ```
1577
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1578
+ @ObservedV2
1579
+ class Info {
1580
+ public name: string = 'John';
1581
+ @Trace public age: number = 24;
1582
+ get myAge() {
1583
+ return this.age; // age为状态变量
1584
+ }
1585
+ // 监听非@Computed装饰的getter访问器
1586
+ @Monitor('myAge')
1587
+ onPropertyChange() {
1588
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'age changed');
1589
+ }
1590
+ }
1591
+ @Entry
1592
+ @ComponentV2
1593
+ struct Index {
1594
+ info: Info = new Info();
1595
+ build() {
1596
+ Column() {
1597
+ Button('change age')
1598
+ .onClick(() => {
1599
+ this.info.age = 25; // 状态变量age改变
1600
+ })
1601
+ }
1602
+ }
1603
+ }
1604
+ ```
1605
+ 上面的代码中,@Monitor的入参为一个getter访问器的名字,但该getter访问器本身并未被@Computed装饰,不是一个可被监听的变量。但由于使用了状态变量参与了计算,在状态变量变化后,myAge也被认为发生了变化,因此触发了@Monitor回调。建议开发者给myAge添加@Computed装饰器或当getter访问器直接返回状态变量时,不监听getter访问器而是直接监听状态变量本身。
1606
+ 【正例2】
1607
+ 将myAge变为状态变量:
1608
+ ```
1609
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1610
+ @ObservedV2
1611
+ class Info {
1612
+ public name: string = 'John';
1613
+ @Trace public age: number = 24;
1614
+ // 给myAge添加@Computed成为状态变量
1615
+ @Computed
1616
+ get myAge() {
1617
+ return this.age;
1618
+ }
1619
+ // 监听@Computed装饰的getter访问器
1620
+ @Monitor('myAge')
1621
+ onPropertyChange() {
1622
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'age changed');
1623
+ }
1624
+ }
1625
+ @Entry
1626
+ @ComponentV2
1627
+ struct Index {
1628
+ info: Info = new Info();
1629
+ build() {
1630
+ Column() {
1631
+ Button('change age')
1632
+ .onClick(() => {
1633
+ this.info.age = 25; // 状态变量age改变
1634
+ })
1635
+ }
1636
+ }
1637
+ }
1638
+ ```
1639
+ 或直接监听状态变量本身:
1640
+ ```
1641
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1642
+ @ObservedV2
1643
+ class Info {
1644
+ public name: string = 'John';
1645
+ @Trace public age: number = 24;
1646
+ // 监听状态变量age
1647
+ @Monitor('age')
1648
+ onPropertyChange() {
1649
+ hilog.info(0xFF00, 'testTag', '%{public}s', 'age changed');
1650
+ }
1651
+ }
1652
+ @Entry
1653
+ @ComponentV2
1654
+ struct Index {
1655
+ info: Info = new Info();
1656
+ build() {
1657
+ Column() {
1658
+ Button('change age')
1659
+ .onClick(() => {
1660
+ this.info.age = 25; // 状态变量age改变
1661
+ })
1662
+ }
1663
+ }
1664
+ }
1665
+ ```
1666
+ #### 无法监听变量从可访问变为不可访问和从不可访问变为可访问
1667
+ @Monitor仅会保存变量可访问时的值,当状态变量变为不可访问的状态时,并不会记录其值的变化。在下面的例子中,点击三个Button,均不会触发onChange的回调。
1668
+ 从API version 20开始,如果需要监听可访问到不可访问和不可访问到可访问的状态变化,可以使用 [addMonitor](D:/code/APIDevice/output/md_output/harmonyos-guides/应用框架/ArkUI(方舟UI框架)/UI开发 (ArkTS声明式开发范式)/学习UI范式状态管理/辅助接口/addMonitor_clearMonitor接口:动态添加_取消监听/arkts-new-addmonitor-clearmonitor.md) 。
1669
+ ```
1670
+ import { hilog } from '@kit.PerformanceAnalysisKit';
1671
+ @ObservedV2
1672
+ class User {
1673
+ @Trace public age: number = 10;
1674
+ }
1675
+ @Entry
1676
+ @ComponentV2
1677
+ struct Page {
1678
+ @Local user: User | undefined | null = new User();
1679
+ @Monitor('user.age')
1680
+ onChange(mon: IMonitor) {
1681
+ mon.dirty.forEach((path: string) => {
1682
+ hilog.info(0xFF00, 'testTag', '%{public}s',
1683
+ `onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`);
1684
+ });
1685
+ }
1686
+ build() {
1687
+ Column() {
1688
+ Text(`User age ${this.user?.age}`).fontSize(20)
1689
+ Button('set user to undefined').onClick(() => {
1690
+ // age:可访问 -> 不可访问
1691
+ this.user = undefined;
1692
+ })
1693
+ Button('set user to User').onClick(() => {
1694
+ // age:不可访问 ->可访问
1695
+ this.user = new User();
1696
+ })
1697
+ Button('set user to null').onClick(() => {
1698
+ // age:可访问->不可访问
1699
+ this.user = null;
1700
+ })
1701
+ }
1702
+ }
1703
+ }
1704
+ ```