1# \@Monitor装饰器:状态变量修改监听 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiyujia926--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9为了增强状态管理框架对状态变量变化的监听能力,开发者可以使用\@Monitor装饰器对状态变量进行监听。 10 11 12\@Monitor提供了对V2状态变量的监听。在阅读本文档前,建议提前阅读:[\@ComponentV2](./arkts-new-componentV2.md),[\@ObservedV2和\@Trace](./arkts-new-observedV2-and-trace.md),[\@Local](./arkts-new-local.md)。 13 14>**说明:** 15> 16> \@Monitor装饰器从API version 12开始支持。 17> 18> 从API version 12开始,该装饰器支持在原子化服务中使用。 19 20## 概述 21 22\@Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力: 23 24- \@Monitor装饰器支持在\@ComponentV2装饰的自定义组件中使用,未被状态变量装饰器[\@Local](arkts-new-local.md)、[\@Param](arkts-new-param.md)、[\@Provider](arkts-new-Provider-and-Consumer.md)、[\@Consumer](arkts-new-Provider-and-Consumer.md)、[\@Computed](arkts-new-Computed.md)装饰的变量无法被\@Monitor监听到变化。 25 26- \@Monitor装饰器支持在类中与[\@ObservedV2、\@Trace](arkts-new-observedV2-and-trace.md)配合使用,不允许在未被\@ObservedV2装饰的类中使用\@Monitor装饰器。未被\@Trace装饰的属性无法被\@Monitor监听到变化。 27- 当观测的属性变化时,\@Monitor装饰器定义的回调方法将被调用。判断属性是否变化使用的是严格相等(===),当严格相等判断的结果是false(即不相等)的情况下,就会触发\@Monitor的回调。当在一次事件中多次改变同一个属性时,将会使用初始值和最终值进行比较以判断是否变化。 28- 单个\@Monitor装饰器能够同时监听多个属性的变化,当这些属性在一次事件中共同变化时,只会触发一次\@Monitor的回调方法。 29- \@Monitor装饰器具有深度监听的能力,能够监听嵌套类、多维数组、对象数组中指定项的变化。对于嵌套类、对象数组中成员属性变化的监听要求该类被\@ObservedV2装饰且该属性被\@Trace装饰。 30- 当\@Monitor监听整个数组时,更改数组的某一项不会被监听到。无法监听内置类型(Array、Map、Date、Set)的API调用引起的变化。 31- 在继承类场景中,可以在父子组件中对同一个属性分别定义\@Monitor进行监听,当属性变化时,父子组件中定义的\@Monitor回调均会被调用。 32- 和[\@Watch装饰器](arkts-watch.md)类似,开发者需要自己定义回调函数,区别在于\@Watch装饰器将函数名作为参数,而\@Monitor直接装饰回调函数。\@Monitor与\@Watch的对比可以查看[\@Monitor与\@Watch的对比](#monitor与watch对比)。 33 34## 状态管理V1版本\@Watch装饰器的局限性 35 36现有状态管理V1版本无法实现对对象、数组中某一单个属性或数组项变化的监听,且无法获取变化之前的值。 37 38```ts 39@Observed 40class Info { 41 name: string = 'Tom'; 42 age: number = 25; 43} 44@Entry 45@Component 46struct Index { 47 @State @Watch('onInfoChange') info: Info = new Info(); 48 @State @Watch('onNumArrChange') numArr: number[] = [1,2,3,4,5]; 49 50 onInfoChange() { 51 console.info(`info after change name: ${this.info.name}, age: ${this.info.age} `); 52 } 53 onNumArrChange() { 54 console.info(`numArr after change ${this.numArr}`); 55 } 56 build() { 57 Row() { 58 Column() { 59 Button('change info name') 60 .onClick(() => { 61 this.info.name = 'Jack'; 62 }) 63 Button('change info age') 64 .onClick(() => { 65 this.info.age = 30; 66 }) 67 Button('change numArr[2]') 68 .onClick(() => { 69 this.numArr[2] = 5; 70 }) 71 Button('change numArr[3]') 72 .onClick(() => { 73 this.numArr[3] = 6; 74 }) 75 } 76 .width('100%') 77 } 78 .height('100%') 79 } 80} 81``` 82 83上述代码中,点击"change info name"更改info中的name属性或点击"change info age"更改age时,均会触发info注册的\@Watch回调。点击"change numArr[2]"更改numArr中的第3个元素或点击"change numArr[3]"更改第4个元素时,均会触发numArr注册的\@Watch回调。在这两个回调中,由于无法获取数据更改前的值,在业务逻辑更加复杂的场景下,无法准确知道是哪一个属性或元素发生了改变从而触发了\@Watch事件,这不便于开发者对变量的更改进行准确监听。因此推出\@Monitor装饰器实现对对象、数组中某一单个属性或数组项变化的监听,并且能够获取到变化之前的值。 84 85## 装饰器说明 86 87| \@Monitor属性装饰器 | 说明 | 88| ------------------- | ------------------------------------------------------------ | 89| 装饰器参数 | 字符串类型的对象属性名。可同时监听多个对象属性,每个属性以逗号隔开,例如@Monitor('prop1', 'prop2')。可监听深层的属性变化,如多维数组中的某一个元素,嵌套对象或对象数组中的某一个属性。详见[监听变化](#监听变化)。 | 90| 装饰对象 | \@Monitor装饰成员方法。当监听的属性发生变化时,会触发该回调方法。该回调方法以[IMonitor类型](../../reference/apis-arkui/arkui-ts/ts-state-management-watch-monitor.md#imonitor12)的变量作为参数,开发者可以从该参数中获取变化前后的相关信息。 | 91 92## 接口说明 93 94IMonitor类型和IMonitorValue\<T\>类型的接口说明参考API文档:[状态变量变化监听](../../reference/apis-arkui/arkui-ts/ts-state-management-watch-monitor.md)。 95 96## 监听变化 97 98### 在\@ComponentV2装饰的自定义组件中使用\@Monitor 99 100使用\@Monitor监听的状态变量发生变化时,会触发\@Monitor的回调方法。 101 102- \@Monitor监听的变量需要被\@Local、\@Param、\@Provider、\@Consumer、\@Computed装饰,未被状态变量装饰器装饰的变量在变化时无法被监听。\@Monitor可以同时监听多个状态变量,这些变量名之间用","隔开。 103 104 ```ts 105 @Entry 106 @ComponentV2 107 struct Index { 108 @Local message: string = 'Hello World'; 109 @Local name: string = 'Tom'; 110 @Local age: number = 24; 111 @Monitor('message', 'name') 112 onStrChange(monitor: IMonitor) { 113 monitor.dirty.forEach((path: string) => { 114 console.info(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 115 }); 116 } 117 build() { 118 Column() { 119 Button('change string') 120 .onClick(() => { 121 this.message += '!'; 122 this.name = 'Jack'; 123 }) 124 } 125 } 126 } 127 ``` 128 129- \@Monitor监听的状态变量为类对象时,仅能监听对象整体的变化。监听类属性的变化需要类属性被\@Trace装饰。 130 131 ```ts 132 class Info { 133 name: string; 134 age: number; 135 constructor(name: string, age: number) { 136 this.name = name; 137 this.age = age; 138 } 139 } 140 @Entry 141 @ComponentV2 142 struct Index { 143 @Local info: Info = new Info('Tom', 25); 144 @Monitor('info') 145 infoChange(monitor: IMonitor) { 146 console.info(`info change`); 147 } 148 @Monitor('info.name') 149 infoPropertyChange(monitor: IMonitor) { 150 console.info(`info name change`); 151 } 152 build() { 153 Column() { 154 Text(`name: ${this.info.name}, age: ${this.info.age}`) 155 Button('change info') 156 .onClick(() => { 157 this.info = new Info('Lucy', 18); // 能够监听到 158 }) 159 Button('change info.name') 160 .onClick(() => { 161 this.info.name = 'Jack'; // 监听不到 162 }) 163 } 164 } 165 } 166 ``` 167 168### 在\@ObservedV2装饰的类中使用\@Monitor 169 170使用\@Monitor监听的属性发生变化时,会触发\@Monitor的回调方法。 171 172- \@Monitor监听的对象属性需要被\@Trace装饰,未被\@Trace装饰的属性的变化无法被监听。\@Monitor可以同时监听多个属性,这些属性之间用","隔开。 173 174 ```ts 175 @ObservedV2 176 class Info { 177 @Trace name: string = 'Tom'; 178 @Trace region: string = 'North'; 179 @Trace job: string = 'Teacher'; 180 age: number = 25; 181 // name被@Trace装饰,能够监听变化 182 @Monitor('name') 183 onNameChange(monitor: IMonitor) { 184 console.info(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 185 } 186 // age未被@Trace装饰,不能监听变化 187 @Monitor('age') 188 onAgeChange(monitor: IMonitor) { 189 console.info(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 190 } 191 // region与job均被@Trace装饰,能够监听变化 192 @Monitor('region', 'job') 193 onChange(monitor: IMonitor) { 194 monitor.dirty.forEach((path: string) => { 195 console.info(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 196 }) 197 } 198 } 199 @Entry 200 @ComponentV2 201 struct Index { 202 info: Info = new Info(); 203 build() { 204 Column() { 205 Button('change name') 206 .onClick(() => { 207 this.info.name = 'Jack'; // 能够触发onNameChange方法 208 }) 209 Button('change age') 210 .onClick(() => { 211 this.info.age = 26; // 不能够触发onAgeChange方法 212 }) 213 Button('change region') 214 .onClick(() => { 215 this.info.region = 'South'; // 能够触发onChange方法 216 }) 217 Button('change job') 218 .onClick(() => { 219 this.info.job = 'Driver'; // 能够触发onChange方法 220 }) 221 } 222 } 223 } 224 ``` 225 226- \@Monitor可以监听深层属性的变化,该深层属性需要被@Trace装饰。 227 228 ```ts 229 @ObservedV2 230 class Inner { 231 @Trace num: number = 0; 232 } 233 @ObservedV2 234 class Outer { 235 inner: Inner = new Inner(); 236 @Monitor('inner.num') 237 onChange(monitor: IMonitor) { 238 console.info(`inner.num change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 239 } 240 } 241 @Entry 242 @ComponentV2 243 struct Index { 244 outer: Outer = new Outer(); 245 build() { 246 Column() { 247 Button('change num') 248 .onClick(() => { 249 this.outer.inner.num = 100; // 能够触发onChange方法 250 }) 251 } 252 } 253 } 254 ``` 255 256- 在继承类场景下,可以在继承链中对同一个属性进行多次监听。 257 258 ```ts 259 @ObservedV2 260 class Base { 261 @Trace name: string; 262 // 基类监听name属性 263 @Monitor('name') 264 onBaseNameChange(monitor: IMonitor) { 265 console.info(`Base Class name change`); 266 } 267 constructor(name: string) { 268 this.name = name; 269 } 270 } 271 @ObservedV2 272 class Derived extends Base { 273 // 继承类监听name属性 274 @Monitor('name') 275 onDerivedNameChange(monitor: IMonitor) { 276 console.info(`Derived Class name change`); 277 } 278 constructor(name: string) { 279 super(name); 280 } 281 } 282 @Entry 283 @ComponentV2 284 struct Index { 285 derived: Derived = new Derived('AAA'); 286 build() { 287 Column() { 288 Button('change name') 289 .onClick(() => { 290 this.derived.name = 'BBB'; // 能够先后触发onBaseNameChange、onDerivedNameChange方法 291 }) 292 } 293 } 294 } 295 ``` 296 297### 通用监听能力 298 299\@Monitor还有一些通用的监听能力。 300 301- \@Monitor支持对数组中的项进行监听,包括多维数组,对象数组。\@Monitor无法监听内置类型(Array、Map、Date、Set)的API调用引起的变化。当\@Monitor监听数组整体时,只能观测到数组整体的赋值。可以通过监听数组的长度变化来判断数组是否有插入、删除等变化。当前仅支持使用"."的方式表达深层属性、数组项的监听。 302 303 ```ts 304 @ObservedV2 305 class Info { 306 @Trace name: string; 307 @Trace age: number; 308 309 constructor(name: string, age: number) { 310 this.name = name; 311 this.age = age; 312 } 313 } 314 @ObservedV2 315 class ArrMonitor { 316 @Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]]; 317 @Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]]; 318 @Trace infoArr: Info[] = [new Info('Jack', 24), new Info('Lucy', 18)]; 319 // dimensionTwo为二维简单类型数组,且被@Trace装饰,能够观测里面的元素变化 320 @Monitor('dimensionTwo.0.0', 'dimensionTwo.1.1') 321 onDimensionTwoChange(monitor: IMonitor) { 322 monitor.dirty.forEach((path: string) => { 323 console.info(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 324 }) 325 } 326 // dimensionThree为三维简单类型数组,且被@Trace装饰,能够观测里面的元素变化 327 @Monitor('dimensionThree.0.0.0', 'dimensionThree.1.1.0') 328 onDimensionThreeChange(monitor: IMonitor) { 329 monitor.dirty.forEach((path: string) => { 330 console.info(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 331 }) 332 } 333 // Info类中属性name、age均被@Trace装饰,能够监听到变化 334 @Monitor('infoArr.0.name', 'infoArr.1.age') 335 onInfoArrPropertyChange(monitor: IMonitor) { 336 monitor.dirty.forEach((path: string) => { 337 console.info(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 338 }) 339 } 340 // infoArr被@Trace装饰,能够监听到infoArr整体赋值的变化 341 @Monitor('infoArr') 342 onInfoArrChange(monitor: IMonitor) { 343 console.info(`infoArr whole change`); 344 } 345 // 能够监听到infoArr的长度变化 346 @Monitor('infoArr.length') 347 onInfoArrLengthChange(monitor: IMonitor) { 348 console.info(`infoArr length change`); 349 } 350 } 351 @Entry 352 @ComponentV2 353 struct Index { 354 arrMonitor: ArrMonitor = new ArrMonitor(); 355 build() { 356 Column() { 357 Button('Change dimensionTwo') 358 .onClick(() => { 359 // 能够触发onDimensionTwoChange方法 360 this.arrMonitor.dimensionTwo[0][0]++; 361 this.arrMonitor.dimensionTwo[1][1]++; 362 }) 363 Button('Change dimensionThree') 364 .onClick(() => { 365 // 能够触发onDimensionThreeChange方法 366 this.arrMonitor.dimensionThree[0][0][0]++; 367 this.arrMonitor.dimensionThree[1][1][0]++; 368 }) 369 Button('Change info property') 370 .onClick(() => { 371 // 能够触发onInfoArrPropertyChange方法 372 this.arrMonitor.infoArr[0].name = 'Tom'; 373 this.arrMonitor.infoArr[1].age = 19; 374 }) 375 Button('Change whole infoArr') 376 .onClick(() => { 377 // 能够触发onInfoArrChange、onInfoArrPropertyChange、onInfoArrLengthChange方法 378 this.arrMonitor.infoArr = [new Info('Cindy', 8)]; 379 }) 380 Button('Push new info to infoArr') 381 .onClick(() => { 382 // 能够触发onInfoArrPropertyChange、onInfoArrLengthChange方法 383 this.arrMonitor.infoArr.push(new Info('David', 50)); 384 }) 385 } 386 } 387 } 388 ``` 389 390- 对象整体改变,但监听的属性不变时,不触发\@Monitor回调。 391 392 下面的示例按照Step1-Step2-Step3的顺序点击,表现为代码注释中的行为。 393 394 如果只点击Step2或Step3,改变name、age的值,此时会触发onNameChange和onAgeChange方法。 395 396 ```ts 397 @ObservedV2 398 class Info { 399 @Trace person: Person; 400 @Monitor('person.name') 401 onNameChange(monitor: IMonitor) { 402 console.info(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 403 } 404 @Monitor('person.age') 405 onAgeChange(monitor: IMonitor) { 406 console.info(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 407 } 408 constructor(name: string, age: number) { 409 this.person = new Person(name, age); 410 } 411 } 412 @ObservedV2 413 class Person { 414 @Trace name: string; 415 @Trace age: number; 416 constructor(name: string, age: number) { 417 this.name = name; 418 this.age = age; 419 } 420 } 421 @Entry 422 @ComponentV2 423 struct Index { 424 info: Info = new Info('Tom', 25); 425 build() { 426 Column() { 427 Button('Step1: Only change name') 428 .onClick(() => { 429 this.info.person = new Person('Jack', 25); // 能够触发onNameChange方法,不触发onAgeChange方法 430 }) 431 Button('Step2: Only change age') 432 .onClick(() => { 433 this.info.person = new Person('Jack', 18); // 能够触发onAgeChange方法,不触发onNameChange方法 434 }) 435 Button('Step3: Change name and age') 436 .onClick(() => { 437 this.info.person = new Person('Lucy', 19); // 能够触发onNameChange、onAgeChange方法 438 }) 439 } 440 } 441 } 442 ``` 443 444- 在一次事件中多次改变被\@Monitor监听的属性,以最后一次修改为准。 445 446 ```ts 447 @ObservedV2 448 class Frequency { 449 @Trace count: number = 0; 450 451 @Monitor('count') 452 onCountChange(monitor: IMonitor) { 453 console.info(`count change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 454 } 455 } 456 457 @Entry 458 @ComponentV2 459 struct Index { 460 frequency: Frequency = new Frequency(); 461 462 build() { 463 Column() { 464 Button('change count to 1000') 465 .onClick(() => { 466 for (let i = 1; i <= 1000; i++) { 467 this.frequency.count = i; 468 } 469 }) 470 Button('change count to 0 then to 1000') 471 .onClick(() => { 472 for (let i = 999; i >= 0; i--) { 473 this.frequency.count = i; 474 } 475 this.frequency.count = 1000; // 最终不触发onCountChange方法 476 }) 477 } 478 } 479 } 480 ``` 481 482在点击按钮"change count to 1000"后,会触发一次onCountChange方法,并输出日志"count change from 0 to 1000"。在点击按钮"change count to 0 then to 1000"后,由于事件前后属性count的值并没有改变,都为1000,所以不触发onCountChange方法。 483 484## 限制条件 485 486使用\@Monitor需要注意如下限制条件: 487 488- 不建议在一个类中对同一个属性进行多次\@Monitor的监听。当一个类中存在对一个属性的多次监听时,只有最后一个定义的监听方法会生效。 489 490 ```ts 491 @ObservedV2 492 class Info { 493 @Trace name: string = 'Tom'; 494 @Monitor('name') 495 onNameChange(monitor: IMonitor) { 496 console.info(`onNameChange`); 497 } 498 @Monitor('name') 499 onNameChangeDuplicate(monitor: IMonitor) { 500 console.info(`onNameChangeDuplicate`); 501 } 502 } 503 @Entry 504 @ComponentV2 505 struct Index { 506 info: Info = new Info(); 507 build() { 508 Column() { 509 Button('change name') 510 .onClick(() => { 511 this.info.name = 'Jack'; // 仅会触发onNameChangeDuplicate方法 512 }) 513 } 514 } 515 } 516 ``` 517 518- 当@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的监听,这是不建议的。 519 520 ```ts 521 @ObservedV2 522 class Info { 523 @Trace name: string = 'Tom'; 524 @Trace age: number = 25; 525 @Trace position: string = 'North'; 526 @Monitor('name', 'age') // Monitor 1 527 onNameAgeChange(monitor: IMonitor) { 528 monitor.dirty.forEach((path: string) => { 529 console.info(`onNameAgeChange path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 530 }); 531 } 532 @Monitor('name', 'position') // Monitor 2 533 onNamePositionChange(monitor: IMonitor) { 534 monitor.dirty.forEach((path: string) => { 535 console.info(`onNamePositionChange path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 536 }); 537 } 538 // 重复监听name、position,仅最后定义的生效 539 @Monitor('name', 'position') // Monitor3 540 onNamePositionChangeDuplicate(monitor: IMonitor) { 541 monitor.dirty.forEach((path: string) => { 542 console.info(`onNamePositionChangeDuplicate path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 543 }); 544 } 545 } 546 @Entry 547 @ComponentV2 548 struct Index { 549 info: Info = new Info(); 550 build() { 551 Column() { 552 Button('change name') 553 .onClick(() => { 554 this.info.name = 'Jack'; // 同时触发onNameAgeChange与onNamePositionChangeDuplicate方法 555 }) 556 } 557 } 558 } 559 ``` 560 561- \@Monitor的参数需要为监听属性名的字符串,仅可以使用字符串字面量、const常量、enum枚举值作为参数。如果使用变量作为参数,仅会监听\@Monitor初始化时,变量值所对应的属性。当更改变量时,\@Monitor无法实时改变监听的属性,即\@Monitor监听的目标属性从初始化时便已经确定,无法动态更改。不建议开发者使用变量作为\@Monitor的参数进行初始化。 562 563 ```ts 564 const t2: string = 't2'; // const常量 565 enum ENUM { 566 T3 = 't3' // enum枚举值 567 }; 568 let t4: string = 't4'; // 变量 569 @ObservedV2 570 class Info { 571 @Trace t1: number = 0; 572 @Trace t2: number = 0; 573 @Trace t3: number = 0; 574 @Trace t4: number = 0; 575 @Trace t5: number = 0; 576 @Monitor('t1') // 字符串字面量 577 onT1Change(monitor: IMonitor) { 578 console.info(`t1 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 579 } 580 @Monitor(t2) 581 onT2Change(monitor: IMonitor) { 582 console.info(`t2 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 583 } 584 @Monitor(ENUM.T3) 585 onT3Change(monitor: IMonitor) { 586 console.info(`t3 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 587 } 588 @Monitor(t4) 589 onT4Change(monitor: IMonitor) { 590 console.info(`t4 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 591 } 592 } 593 @Entry 594 @ComponentV2 595 struct Index { 596 info: Info = new Info(); 597 build() { 598 Column() { 599 Button('Change t1') 600 .onClick(() => { 601 this.info.t1++; // 能够触发onT1Change方法 602 }) 603 Button('Change t2') 604 .onClick(() => { 605 this.info.t2++; // 能够触发onT2Change方法 606 }) 607 Button('Change t3') 608 .onClick(() => { 609 this.info.t3++; // 能够触发onT3Change方法 610 }) 611 Button('Change t4') 612 .onClick(() => { 613 this.info.t4++; // 能够触发onT4Change方法 614 }) 615 Button('Change var t4 to t5') 616 .onClick(() => { 617 t4 = 't5'; // 更改变量值为't5' 618 }) 619 Button('Change t5') 620 .onClick(() => { 621 this.info.t5++; // onT4Change仍监听t4,不会触发 622 }) 623 Button('Change t4 again') 624 .onClick(() => { 625 this.info.t4++; // 能够触发onT4Change方法 626 }) 627 } 628 } 629 } 630 ``` 631 632- 建议开发者避免在\@Monitor中再次更改被监听的属性,这会导致无限循环。 633 634 ```ts 635 @ObservedV2 636 class Info { 637 @Trace count: number = 0; 638 @Monitor('count') 639 onCountChange(monitor: IMonitor) { 640 this.count++; // 应避免这种写法,会导致无限循环 641 } 642 } 643 ``` 644 645## \@Monitor与\@Watch对比 646 647\@Monitor与\@Watch的用法、功能对比如下: 648 649| | \@Watch | \@Monitor | 650| ------------------ | --------------------------------------- | ------------------------------------------------------------ | 651| 参数 | 回调方法名。 | 监听状态变量名、属性名。 | 652| 监听目标数 | 只能监听单个状态变量。 | 能同时监听多个状态变量。 | 653| 监听能力 | 跟随状态变量观察能力(一层)。 | 跟随状态变量观察能力(深层)。 | 654| 能否获取变化前的值 | 不能获取变化前的值。 | 能获取变化前的值。 | 655| 监听条件 | 监听对象为状态变量。 | 监听对象为状态变量或为\@Trace装饰的类成员属性。 | 656| 使用限制 | 仅能在\@Component装饰的自定义组件中使用。 | 能在\@ComponentV2装饰的自定义组件中使用,也能在\@ObservedV2装饰的类中使用。 | 657 658## 使用场景 659 660### 监听深层属性变化 661 662\@Monitor可以监听深层属性的变化,并能够根据更改前后的值做分类处理。 663 664下面的示例中监听了属性value的变化,并根据变化的幅度改变Text组件显示的样式。 665 666```ts 667@ObservedV2 668class Info { 669 @Trace value: number = 50; 670} 671@ObservedV2 672class UIStyle { 673 info: Info = new Info(); 674 @Trace color: Color = Color.Black; 675 @Trace fontSize: number = 45; 676 @Monitor('info.value') 677 onValueChange(monitor: IMonitor) { 678 let lastValue: number = monitor.value()?.before as number; 679 let curValue: number = monitor.value()?.now as number; 680 if (lastValue != 0) { 681 let diffPercent: number = (curValue - lastValue) / lastValue; 682 if (diffPercent > 0.1) { 683 this.color = Color.Red; 684 this.fontSize = 50; 685 } else if (diffPercent < -0.1) { 686 this.color = Color.Green; 687 this.fontSize = 40; 688 } else { 689 this.color = Color.Black; 690 this.fontSize = 45; 691 } 692 } 693 } 694} 695@Entry 696@ComponentV2 697struct Index { 698 textStyle: UIStyle = new UIStyle(); 699 build() { 700 Column() { 701 Text(`Important Value: ${this.textStyle.info.value}`) 702 .fontColor(this.textStyle.color) 703 .fontSize(this.textStyle.fontSize) 704 Button('change!') 705 .onClick(() => { 706 this.textStyle.info.value = Math.floor(Math.random() * 100) + 1; 707 }) 708 } 709 } 710} 711``` 712 713## 常见问题 714 715### 自定义组件中\@Monitor对变量监听的生效及失效时间 716 717当\@Monitor定义在\@ComponentV2装饰的自定义组件中时,\@Monitor会在状态变量初始化完成之后生效,并在组件销毁时失效。 718 719```ts 720@ObservedV2 721class Info { 722 @Trace message: string = 'not initialized'; 723 724 constructor() { 725 console.info('in constructor message change to initialized'); 726 this.message = 'initialized'; 727 } 728} 729@ComponentV2 730struct Child { 731 @Param info: Info = new Info(); 732 @Monitor('info.message') 733 onMessageChange(monitor: IMonitor) { 734 console.info(`Child message change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 735 } 736 aboutToAppear(): void { 737 this.info.message = 'Child aboutToAppear'; 738 } 739 aboutToDisappear(): void { 740 console.info('Child aboutToDisappear'); 741 this.info.message = 'Child aboutToDisappear'; 742 } 743 build() { 744 Column() { 745 Text('Child') 746 Button('change message in Child') 747 .onClick(() => { 748 this.info.message = 'Child click to change Message'; 749 }) 750 } 751 .borderColor(Color.Red) 752 .borderWidth(2) 753 754 } 755} 756@Entry 757@ComponentV2 758struct Index { 759 @Local info: Info = new Info(); 760 @Local flag: boolean = false; 761 @Monitor('info.message') 762 onMessageChange(monitor: IMonitor) { 763 console.info(`Index message change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 764 } 765 766 build() { 767 Column() { 768 Button('show/hide Child') 769 .onClick(() => { 770 this.flag = !this.flag 771 }) 772 Button('change message in Index') 773 .onClick(() => { 774 this.info.message = 'Index click to change Message'; 775 }) 776 if (this.flag) { 777 Child({ info: this.info }) 778 } 779 } 780 } 781} 782``` 783 784在上面的例子中,可以通过创建和销毁Child组件来观察定义在自定义组件中的\@Monitor的生效和失效时机。推荐按如下顺序进行操作: 785 786- 当Index组件创建Info类实例时,日志输出`in constructor message change to initialized`。此时Index组件的\@Monitor还未初始化成功,因此不会监听到message的变化。 787- 当Index组件创建完成,页面加载完成后,点击按钮“change message in Index”,此时Index组件中的\@Monitor能够监听到变化,日志输出`Index message change from initialized to Index click to change Message`。 788- 点击按钮“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`。 789- 点击按钮“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`。 790- 点击按钮”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`。 791- 点击按钮“change message in Index”,改变message。此时Child组件已销毁,其注册的\@Monitor监听也被解注册,仅有Index组件的\@Monitor能够监听到变化,日志输出`Index message change from Child aboutToDisappear to Index click to change Message`。 792 793这表明Child组件中定义的\@Monitor监听随着Child组件的创建初始化生效,随着Child组件的销毁失效。 794 795### 类中\@Monitor对变量监听的生效及失效时间 796 797当\@Monitor定义在\@ObservedV2装饰的类中时,\@Monitor会在类的实例创建完成后生效,在类的实例销毁时失效。 798 799```ts 800@ObservedV2 801class Info { 802 @Trace message: string = 'not initialized'; 803 804 constructor() { 805 this.message = 'initialized'; 806 } 807 @Monitor('message') 808 onMessageChange(monitor: IMonitor) { 809 console.info(`message change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 810 } 811} 812 813@Entry 814@ComponentV2 815struct Index { 816 info: Info = new Info(); 817 818 aboutToAppear(): void { 819 this.info.message = 'Index aboutToAppear'; 820 } 821 822 build() { 823 Column() { 824 Button('change message') 825 .onClick(() => { 826 this.info.message = 'Index click to change message'; 827 }) 828 } 829 } 830} 831``` 832 833上面的例子中,\@Monitor会在info创建完成后生效,这个时机晚于类的constructor,早于自定义组件的aboutToAppear。当界面加载完成后,点击“change message”,修改message变量。此时日志输出信息如下: 834 835```ts 836message change from initialized to Index aboutToAppear 837message change from Index aboutToAppear to Index click to change message 838``` 839 840类中定义的\@Monitor随着类的销毁失效。而由于类的实际销毁释放依赖于垃圾回收机制,因此会出现即使所在自定义组件已经销毁,类却还未及时销毁,导致类中定义的\@Monitor仍在监听变化的情况。 841 842```ts 843@ObservedV2 844class InfoWrapper { 845 info?: Info; 846 constructor(info: Info) { 847 this.info = info; 848 } 849 @Monitor('info.age') 850 onInfoAgeChange(monitor: IMonitor) { 851 console.info(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 852 } 853} 854@ObservedV2 855class Info { 856 @Trace age: number; 857 constructor(age: number) { 858 this.age = age; 859 } 860} 861@ComponentV2 862struct Child { 863 @Param @Require infoWrapper: InfoWrapper; 864 aboutToDisappear(): void { 865 console.info('Child aboutToDisappear', this.infoWrapper.info?.age); 866 } 867 build() { 868 Column() { 869 Text(`${this.infoWrapper.info?.age}`) 870 } 871 } 872} 873@Entry 874@ComponentV2 875struct Index { 876 dataArray: Info[] = []; 877 @Local showFlag: boolean = true; 878 aboutToAppear(): void { 879 for (let i = 0; i < 5; i++) { 880 this.dataArray.push(new Info(i)); 881 } 882 } 883 build() { 884 Column() { 885 Button('change showFlag') 886 .onClick(() => { 887 this.showFlag = !this.showFlag; 888 }) 889 Button('change number') 890 .onClick(() => { 891 console.info('click to change age'); 892 this.dataArray.forEach((info: Info) => { 893 info.age += 100; 894 }); 895 }) 896 if (this.showFlag) { 897 Column() { 898 Text('Childs') 899 ForEach(this.dataArray, (info: Info) => { 900 Child({ infoWrapper: new InfoWrapper(info) }) 901 }) 902 } 903 .borderColor(Color.Red) 904 .borderWidth(2) 905 } 906 } 907 } 908} 909``` 910 911在上面的例子中,当点击“change showFlag”切换if组件的条件时,Child组件会被销毁。此时,点击“change number”修改age的值时,可以通过日志观察到InfoWrapper中定义的\@Monitor回调仍然被触发了。这是因为此时自定义组件Child虽然执行了aboutToDisappear,但是其成员变量infoWrapper还没有被立刻回收,当变量发生变化时,依然能够调用到infoWrapper中定义的onInfoAgeChange方法,所以从现象上看\@Monitor回调仍会被触发。 912 913借助垃圾回收机制去取消\@Monitor的监听是不稳定的,开发者可以采用以下两种方式去管理\@Monitor的失效时间: 914 9151、将\@Monitor定义在自定义组件中。由于自定义组件在销毁时,状态管理框架会手动取消\@Monitor的监听,因此在自定义组件调用完aboutToDisappear,尽管自定义组件的数据不一定已经被释放,但\@Monitor回调已不会再被触发。 916 917```ts 918@ObservedV2 919class InfoWrapper { 920 info?: Info; 921 constructor(info: Info) { 922 this.info = info; 923 } 924} 925@ObservedV2 926class Info { 927 @Trace age: number; 928 constructor(age: number) { 929 this.age = age; 930 } 931} 932@ComponentV2 933struct Child { 934 @Param @Require infoWrapper: InfoWrapper; 935 @Monitor('infoWrapper.info.age') 936 onInfoAgeChange(monitor: IMonitor) { 937 console.info(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 938 } 939 aboutToDisappear(): void { 940 console.info('Child aboutToDisappear', this.infoWrapper.info?.age); 941 } 942 build() { 943 Column() { 944 Text(`${this.infoWrapper.info?.age}`) 945 } 946 } 947} 948@Entry 949@ComponentV2 950struct Index { 951 dataArray: Info[] = []; 952 @Local showFlag: boolean = true; 953 aboutToAppear(): void { 954 for (let i = 0; i < 5; i++) { 955 this.dataArray.push(new Info(i)); 956 } 957 } 958 build() { 959 Column() { 960 Button('change showFlag') 961 .onClick(() => { 962 this.showFlag = !this.showFlag; 963 }) 964 Button('change number') 965 .onClick(() => { 966 console.info('click to change age'); 967 this.dataArray.forEach((info: Info) => { 968 info.age += 100; 969 }) 970 }) 971 if (this.showFlag) { 972 Column() { 973 Text('Childs') 974 ForEach(this.dataArray, (info: Info) => { 975 Child({ infoWrapper: new InfoWrapper(info) }) 976 }) 977 } 978 .borderColor(Color.Red) 979 .borderWidth(2) 980 } 981 } 982 } 983} 984``` 985 9862、主动置空监听的对象。当自定义组件即将销毁时,主动置空\@Monitor的监听目标,这样\@Monitor无法再监听原监听目标的变化,达到取消\@Monitor监听的效果。 987 988```ts 989@ObservedV2 990class InfoWrapper { 991 info?: Info; 992 constructor(info: Info) { 993 this.info = info; 994 } 995 @Monitor('info.age') 996 onInfoAgeChange(monitor: IMonitor) { 997 console.info(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 998 } 999} 1000@ObservedV2 1001class Info { 1002 @Trace age: number; 1003 constructor(age: number) { 1004 this.age = age; 1005 } 1006} 1007@ComponentV2 1008struct Child { 1009 @Param @Require infoWrapper: InfoWrapper; 1010 aboutToDisappear(): void { 1011 console.info('Child aboutToDisappear', this.infoWrapper.info?.age); 1012 this.infoWrapper.info = undefined; // 使InfoWrapper对info.age的监听失效 1013 } 1014 build() { 1015 Column() { 1016 Text(`${this.infoWrapper.info?.age}`) 1017 } 1018 } 1019} 1020@Entry 1021@ComponentV2 1022struct Index { 1023 dataArray: Info[] = []; 1024 @Local showFlag: boolean = true; 1025 aboutToAppear(): void { 1026 for (let i = 0; i < 5; i++) { 1027 this.dataArray.push(new Info(i)); 1028 } 1029 } 1030 build() { 1031 Column() { 1032 Button('change showFlag') 1033 .onClick(() => { 1034 this.showFlag = !this.showFlag; 1035 }) 1036 Button('change number') 1037 .onClick(() => { 1038 console.info('click to change age'); 1039 this.dataArray.forEach((info: Info) => { 1040 info.age += 100; 1041 }) 1042 }) 1043 if (this.showFlag) { 1044 Column() { 1045 Text('Childs') 1046 ForEach(this.dataArray, (info: Info) => { 1047 Child({ infoWrapper: new InfoWrapper(info) }) 1048 }) 1049 } 1050 .borderColor(Color.Red) 1051 .borderWidth(2) 1052 } 1053 } 1054 } 1055} 1056``` 1057 1058### 正确设置\@Monitor入参 1059 1060由于\@Monitor无法对入参做编译时校验,当前存在以下写法不符合\@Monitor监听条件但\@Monitor仍会触发的情况。开发者应当正确传入\@Monitor入参,不传入非状态变量,避免造成功能异常或行为表现不符合预期。 1061 1062【反例1】 1063 1064```ts 1065@ObservedV2 1066class Info { 1067 name: string = 'John'; 1068 @Trace age: number = 24; 1069 @Monitor('age', 'name') // 同时监听状态变量age和非状态变量name 1070 onPropertyChange(monitor: IMonitor) { 1071 monitor.dirty.forEach((path: string) => { 1072 console.info(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 1073 }) 1074 } 1075} 1076@Entry 1077@ComponentV2 1078struct Index { 1079 info: Info = new Info(); 1080 build() { 1081 Column() { 1082 Button('change age&name') 1083 .onClick(() => { 1084 this.info.age = 25; // 同时改变状态变量age和非状态变量name 1085 this.info.name = 'Johny'; 1086 }) 1087 } 1088 } 1089} 1090``` 1091 1092上面的代码中,当点击按钮同时更改状态变量age和非状态变量name时,会输出以下日志: 1093 1094``` 1095property path:age change from 24 to 25 1096property path:name change from John to Johny 1097``` 1098 1099实际上name属性本身并不是可被观测的变量,不应被加入到\@Monitor的入参当中。建议开发者去除对name属性的监听或者给name加上\@Trace装饰成为状态变量。 1100 1101【正例1】 1102 1103```ts 1104@ObservedV2 1105class Info { 1106 name: string = 'John'; 1107 @Trace age: number = 24; 1108 @Monitor('age') // 仅监听状态变量age 1109 onPropertyChange(monitor: IMonitor) { 1110 monitor.dirty.forEach((path: string) => { 1111 console.info(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 1112 }) 1113 } 1114} 1115@Entry 1116@ComponentV2 1117struct Index { 1118 info: Info = new Info(); 1119 build() { 1120 Column() { 1121 Button('change age&name') 1122 .onClick(() => { 1123 this.info.age = 25; // 状态变量age改变 1124 this.info.name = 'Johny'; 1125 }) 1126 } 1127 } 1128} 1129``` 1130 1131【反例2】 1132 1133```ts 1134@ObservedV2 1135class Info { 1136 name: string = 'John'; 1137 @Trace age: number = 24; 1138 get myAge() { 1139 return this.age; // age为状态变量 1140 } 1141 @Monitor('myAge') // 监听非@Computed装饰的getter访问器 1142 onPropertyChange() { 1143 console.info('age changed'); 1144 } 1145} 1146@Entry 1147@ComponentV2 1148struct Index { 1149 info: Info = new Info(); 1150 build() { 1151 Column() { 1152 Button('change age') 1153 .onClick(() => { 1154 this.info.age = 25; // 状态变量age改变 1155 }) 1156 } 1157 } 1158} 1159``` 1160 1161上面的代码中,\@Monitor的入参为一个getter访问器的名字,但该getter访问器本身并未被\@Computed装饰,不是一个可被监听的变量。但由于使用了状态变量参与了计算,在状态变量变化后,myAge也被认为发生了变化,因此触发了\@Monitor回调。建议开发者给myAge添加\@Computed装饰器或当getter访问器直接返回状态变量时,不监听getter访问器而是直接监听状态变量本身。 1162 1163【正例2】 1164 1165将myAge变为状态变量: 1166 1167```ts 1168@ObservedV2 1169class Info { 1170 name: string = 'John'; 1171 @Trace age: number = 24; 1172 @Computed // 给myAge添加@Computed成为状态变量 1173 get myAge() { 1174 return this.age; 1175 } 1176 @Monitor('myAge') // 监听@Computed装饰的getter访问器 1177 onPropertyChange() { 1178 console.info('age changed'); 1179 } 1180} 1181@Entry 1182@ComponentV2 1183struct Index { 1184 info: Info = new Info(); 1185 build() { 1186 Column() { 1187 Button('change age') 1188 .onClick(() => { 1189 this.info.age = 25; // 状态变量age改变 1190 }) 1191 } 1192 } 1193} 1194``` 1195 1196或直接监听状态变量本身: 1197 1198```ts 1199@ObservedV2 1200class Info { 1201 name: string = 'John'; 1202 @Trace age: number = 24; 1203 @Monitor('age') // 监听状态变量age 1204 onPropertyChange() { 1205 console.info('age changed'); 1206 } 1207} 1208@Entry 1209@ComponentV2 1210struct Index { 1211 info: Info = new Info(); 1212 build() { 1213 Column() { 1214 Button('change age') 1215 .onClick(() => { 1216 this.info.age = 25; // 状态变量age改变 1217 }) 1218 } 1219 } 1220} 1221``` 1222### 无法监听变量从可访问变为不可访问和从不可访问变为可访问 1223\@Monitor仅会保存变量可访问时的值,当状态变量变为不可访问的状态时,并不会记录其值的变化。在下面的例子中,点击三个Button,均不会触发`onChange`的回调。 1224如果需要监听可访问到不可访问和不可访问到可访问的状态变化,可以使用[addMonitor](./arkts-new-addMonitor-clearMonitor.md#监听变量从可访问到不访问和从不可访问到可访问)。 1225 1226```ts 1227@ObservedV2 1228class User { 1229 @Trace age: number = 10; 1230} 1231 1232@Entry 1233@ComponentV2 1234struct Page { 1235 @Local user: User | undefined | null = new User(); 1236 1237 @Monitor('user.age') 1238 onChange(mon: IMonitor) { 1239 mon.dirty.forEach((path: string) => { 1240 console.info(`onChange: User property ${path} change from ${mon.value(path)?.before} to ${mon.value(path)?.now}`); 1241 }); 1242 } 1243 1244 build() { 1245 Column() { 1246 Text(`User age ${this.user?.age}`).fontSize(20) 1247 Button('set user to undefined').onClick(() => { 1248 // age:可访问 -> 不可访问 1249 this.user = undefined; 1250 }) 1251 Button('set user to User').onClick(() => { 1252 // age:不可访问 ->可访问 1253 this.user = new User(); 1254 }) 1255 Button('set user to null').onClick(() => { 1256 // age:可访问->不可访问 1257 this.user = null; 1258 }) 1259 } 1260 } 1261} 1262``` 1263