1# \@Monitor Decorator: Listening for Value Changes of the State Variables 2 3You can use \@Monitor, a method decorator in state management V2, to enhance the capability of the state management framework to listen for the state variable changes 4 5>**NOTE** 6> 7>The \@Monitor decorator is supported since API version 12. 8> 9 10## Overview 11 12To listen for value changes of the state variables in a lower level, you can use the \@Monitor decorator: 13 14- The \@Monitor decorator can be used in custom components decorated by \@ComponentV2. But it cannot listen for the changes of the state variables that are not decorated by these decorators: [\@Local](arkts-new-local.md), [\@Param](arkts-new-param.md), [\@Provider](arkts-new-Provider-and-Consumer.md), [\@Consumer](arkts-new-Provider-and-Consumer.md), and [\@Computed](arkts-new-Computed.md). 15 16- The \@Monitor decorator can be used in a class together with [\@ObservedV2 and \@Trace](arkts-new-observedV2-and-trace.md) decorators. But it cannot be used in a class that is not decorated by \@ObservedV2. \@Monitor cannot listen for the properties that are not decorated by \@Trace. 17- When the listened property changes, the callback defined by \@Monitor will be called. Strict equality (===) is used to determine whether a property changes. If **false** is returned, the \@Monitor callback is triggered. When a property is changed for multiple times in an event, the initial value will be compared with the final value to determine whether the property is changed. 18- A single \@Monitor decorator can listen for the changes of multiple properties at the same time. When these properties change together in an event, the \@Monitor callback method is triggered only once. 19- The \@Monitor decorator has lower-level listening capability and can listen for changes of specified items in nested classes, multi-dimensional arrays, and object arrays. The observation requires that \@ObservedV2 decorate the nested class and \@Trace decorate the member properties in an object array. 20- In the inheritance scenario, you can define \@Monitor for the same property in the parent and child components for listening. When the property changes, the \@Monitor callback defined in the parent and child components is called. 21- Similar to the [\@Watch](arkts-watch.md) decorator, you should define the callback functions by yourselves. The difference is that the \@Watch decorator uses the function name as a parameter, while the \@Monitor directly decorates the callback function. For details about the comparison between \@Monitor and \@Watch, see [Comparing \@Monitor with \@Watch](#comparing-monitor-with-watch). 22 23## Limitations of the \@Watch decorator in State Management V1 24 25This V1 version cannot listen for the changes of an object, a single property in an array, or array items. It also cannot obtain the value before change. 26 27```ts 28@Observed 29class Info { 30 name: string = "Tom"; 31 age: number = 25; 32} 33@Entry 34@Component 35struct Index { 36 @State @Watch('onInfoChange') info: Info = new Info(); 37 @State @Watch('onNumArrChange') numArr: number[] = [1,2,3,4,5]; 38 39 onInfoChange() { 40 console.log(`info after change name: ${this.info.name}, age: ${this.info.age} `); 41 } 42 onNumArrChange() { 43 console.log(`numArr after change ${JSON.stringify(this.numArr)}`); 44 } 45 build() { 46 Row() { 47 Column() { 48 Button("change info name") 49 .onClick(() => { 50 this.info.name = "Jack"; 51 }) 52 Button("change info age") 53 .onClick(() => { 54 this.info.age = 30; 55 }) 56 Button("change numArr[2]") 57 .onClick(() => { 58 this.numArr[2] = 5; 59 }) 60 Button("change numArr[3]") 61 .onClick(() => { 62 this.numArr[3] = 6; 63 }) 64 } 65 .width('100%') 66 } 67 .height('100%') 68 } 69} 70``` 71 72In the preceding code, when you click **change info name** to change the **name** property in **info**, or click **change info age** to change **age**, the **info** registered \@Watch callback is triggered. When you click **change numArr[2]** to change the third element in **numArr**, or click **change numArr[3]** to change the fourth element, the **numArr** registered \@Watch callback is triggered. In these two callbacks, the value before data change cannot be obtained. This makes it inconvenient for you to listen for the variable changes because you cannot find out which property or element is changed to trigger \@Watch event in a more complex scenario. Therefore, the \@Monitor decorator comes into the picture to listen for the changes of an object, a single property in an array, or an array item and obtain the value before change. 73 74## Decorator Description 75 76| \@Monitor Property Decorator| Description | 77| ------------------- | ------------------------------------------------------------ | 78| Decorator parameter | Object property name of the string type. This decorator can listen for multiple object properties at the same time. Each property is separated by commas (,), for example, @Monitor ("prop1", "prop2"). In addition, properties such as an element in a multi-dimensional array, a property in a nested object, and a property in an object array can be listened in a lower level. For details, see [Listened Changes](#listened-changes).| 79| Decorated object | \@Monitor decorated member method. This callback is triggered when the listened property changes. The variable of [IMonitor type](#imonitor-type) will be set as a parameter to provide related information before and after change.| 80 81## API Description 82 83### IMonitor Type 84 85Variables of the IMonitor type are used as parameters for \@Monitor to decorate a method. 86 87| Property | Type | Parameter | Return Value | Description | 88| ---------- | --------------- | ------------- | ------------------ | ------------------------------------------------------------ | 89| dirty | Array\<string\> | None. | None. | Saves the changed property name. | 90| value\<T\> | function | path?: string | IMonitorValue\<T\> | Obtains the change information of a specified property (**path**). If **path** is not specified, @Monitor will return the first changed property information in the listening sequence.| 91 92### IMonitorValue\<T\> Type 93 94Saves the information about property changes, including the property name, original value, and new value. 95 96| Property | Type | Description | 97| ------ | ------ | -------------------------- | 98| before | T | Listens for the value before the property change. | 99| now | T | Listens for the current value after the property changes.| 100| path | string | Listened property name. | 101 102## Listened Changes 103 104### Using \@Monitor in Custom Components Decorated by \@ComponentV2 105 106When the state variables listened by \@Monitor change, the callback is triggered. 107 108- Variables listened by \@Monitor need to be decorated by \@Local, \@Param, \@Provider, \@Consumer, and \@Computed. Otherwise, they cannot be listened when they change. \@Monitor can listen for multiple state variables at the same time. Names of these variables are separated by commas (,). 109 110 ```ts 111 @Entry 112 @ComponentV2 113 struct Index { 114 @Local message: string = "Hello World"; 115 @Local name: string = "Tom"; 116 @Local age: number = 24; 117 @Monitor("message", "name") 118 onStrChange(monitor: IMonitor) { 119 monitor.dirty.forEach((path: string) => { 120 console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`) 121 }) 122 } 123 build() { 124 Column() { 125 Button("change string") 126 .onClick(() => { 127 this.message += "!"; 128 this.name = "Jack"; 129 }) 130 } 131 } 132 } 133 ``` 134 135- When the state variable listened by \@Monitor is a class object, only the overall object changes can be listened. To listen for the changes of a class property, this property should be decorated by \@Trace. 136 137 ```ts 138 class Info { 139 name: string; 140 age: number; 141 constructor(name: string, age: number) { 142 this.name = name; 143 this.age = age; 144 } 145 } 146 @Entry 147 @ComponentV2 148 struct Index { 149 @Local info: Info = new Info("Tom", 25); 150 @Monitor("info") 151 infoChange(monitor: IMonitor) { 152 console.log(`info change`); 153 } 154 @Monitor("info.name") 155 infoPropertyChange(monitor: IMonitor) { 156 console.log(`info name change`); 157 } 158 build() { 159 Column() { 160 Text(`name: ${this.info.name}, age: ${this.info.age}`) 161 Button("change info") 162 .onClick(() => { 163 this.info = new Info ("Lucy", 18); // Can listen for the change. 164 }) 165 Button("change info.name") 166 .onClick(() => { 167 this.info.name = "Jack"; // Cannot listen for the change. 168 }) 169 } 170 } 171 } 172 ``` 173 174### Using \@Monitor in Classes Decorated by \@ObservedV2 175 176When the properties listened by \@Monitor change, the callback is triggered. 177 178- The object property listened by \@Monitor should be decorated by \@Trace. Otherwise, the property cannot be listened. \@Monitor can listen for multiple properties at the same time. These properties are separated by commas (,). 179 180```ts 181@ObservedV2 182class Info { 183 @Trace name: string = "Tom"; 184 @Trace region: string = "North"; 185 @Trace job: string = "Teacher"; 186 age: number = 25; 187 // name is decorated by @Trace. Can listen for the change. 188 @Monitor("name") 189 onNameChange(monitor: IMonitor) { 190 console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 191 } 192 // age is not decorated by @Trace. Cannot listen for the change. 193 @Monitor("age") 194 onAgeChange(monitor: IMonitor) { 195 console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 196 } 197 // region and job are decorated by @Trace. Can listen for the change. 198 @Monitor("region", "job") 199 onChange(monitor: IMonitor) { 200 monitor.dirty.forEach((path: string) => { 201 console.log(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 202 }) 203 } 204} 205@Entry 206@ComponentV2 207struct Index { 208 info: Info = new Info(); 209 build() { 210 Column() { 211 Button("change name") 212 .onClick(() => { 213 this.info.name = "Jack"; // Can trigger the onNameChange method. 214 }) 215 Button("change age") 216 .onClick(() => { 217 this.info.age = 26; // Cannot trigger the onAgeChange method. 218 }) 219 Button("change region") 220 .onClick(() => { 221 this.info.region = "South"; // Can trigger the onChange method. 222 }) 223 Button("change job") 224 .onClick(() => { 225 this.info.job = "Driver"; // Can trigger the onChange method. 226 }) 227 } 228 } 229} 230``` 231 232- \@Monitor can listen for the changes of lower-level properties which should be decorated by @Trace. 233 234```ts 235@ObservedV2 236class Inner { 237 @Trace num: number = 0; 238} 239@ObservedV2 240class Outer { 241 inner: Inner = new Inner(); 242 @Monitor("inner.num") 243 onChange(monitor: IMonitor) { 244 console.log(`inner.num change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 245 } 246} 247@Entry 248@ComponentV2 249struct Index { 250 outer: Outer = new Outer(); 251 build() { 252 Column() { 253 Button("change name") 254 .onClick(() => { 255 this.outer.inner.num = 100; // Can trigger the onChange method. 256 }) 257 } 258 } 259} 260``` 261 262- In the inheritance class scenario, you can listen for the same property for multiple times in the inheritance chain. 263 264```ts 265@ObservedV2 266class Base { 267 @Trace name: string; 268 // Listen for the name property of the base class. 269 @Monitor("name") 270 onBaseNameChange(monitor: IMonitor) { 271 console.log(`Base Class name change`); 272 } 273 constructor(name: string) { 274 this.name = name; 275 } 276} 277@ObservedV2 278class Derived extends Base { 279 // Listen for the name property of the inheritance class. 280 @Monitor("name") 281 onDerivedNameChange(monitor: IMonitor) { 282 console.log(`Derived Class name change`); 283 } 284 constructor(name: string) { 285 super(name); 286 } 287} 288@Entry 289@ComponentV2 290struct Index { 291 derived: Derived = new Derived("AAA"); 292 build() { 293 Column() { 294 Button("change name") 295 .onClick(() => { 296 this.derived.name = "BBB"; // Can trigger the onBaseNameChange and onDerivedNameChange methods in sequence. 297 }) 298 } 299 } 300} 301``` 302 303### General Listening Capability 304 305\@Monitor also has some general listening capabilities. 306 307- \@Monitor can listen for items in arrays, including multi-dimensional arrays and object arrays. \@Monitor cannot listen for changes caused by calling APIs of built-in types (Array, Map, Date, and Set). When \@Monitor listens for the entire array, only the value changes to the entire array can be observed. But you can listen for the length change of the array to determine whether the array is inserted or deleted. Currently, only periods (.) can be used to listen for lower-level properties and array items. 308 309```ts 310@ObservedV2 311class Info { 312 @Trace name: string; 313 @Trace age: number; 314 315 constructor(name: string, age: number) { 316 this.name = name; 317 this.age = age; 318 } 319} 320@ObservedV2 321class ArrMonitor { 322 @Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]]; 323 @Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]]; 324 @Trace infoArr: Info[] = [new Info("Jack", 24), new Info("Lucy", 18)]; 325 // dimensionTwo is a two-dimensional simple array and is decorated by @Trace. Can observe the element changes. 326 @Monitor("dimensionTwo.0.0", "dimensionTwo.1.1") 327 onDimensionTwoChange(monitor: IMonitor) { 328 monitor.dirty.forEach((path: string) => { 329 console.log(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 330 }) 331 } 332 // dimensionThree is a three-dimensional simple array and is decorated by @Trace. Can observe the element changes. 333 @Monitor("dimensionThree.0.0.0", "dimensionThree.1.1.0") 334 onDimensionThreeChange(monitor: IMonitor) { 335 monitor.dirty.forEach((path: string) => { 336 console.log(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 337 }) 338 } 339 // name and age properties of the info class are decorated by @Trace. Can listen for the change. 340 @Monitor("infoArr.0.name", "infoArr.1.age") 341 onInfoArrPropertyChange(monitor: IMonitor) { 342 monitor.dirty.forEach((path: string) => { 343 console.log(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 344 }) 345 } 346 // infoArr is decorated by @Trace. Can listen for the value changes. 347 @Monitor("infoArr") 348 onInfoArrChange(monitor: IMonitor) { 349 console.log(`infoArr whole change`); 350 } 351 // Can listen for the length change of the infoArr. 352 @Monitor("infoArr.length") 353 onInfoArrLengthChange(monitor: IMonitor) { 354 console.log(`infoArr length change`); 355 } 356} 357@Entry 358@ComponentV2 359struct Index { 360 arrMonitor: ArrMonitor = new ArrMonitor(); 361 build() { 362 Column() { 363 Button("Change dimensionTwo") 364 .onClick(() => { 365 // Can trigger the onDimensionTwoChange method. 366 this.arrMonitor.dimensionTwo[0][0]++; 367 this.arrMonitor.dimensionTwo[1][1]++; 368 }) 369 Button("Change dimensionThree") 370 .onClick(() => { 371 // Can trigger the onDimensionThreeChange method. 372 this.arrMonitor.dimensionThree[0][0][0]++; 373 this.arrMonitor.dimensionThree[1][1][0]++; 374 }) 375 Button("Change info property") 376 .onClick(() => { 377 // Can trigger the onInfoArrPropertyChange method. 378 this.arrMonitor.infoArr[0].name = "Tom"; 379 this.arrMonitor.infoArr[1].age = 19; 380 }) 381 Button("Change whole infoArr") 382 .onClick(() => { 383 // Can trigger the onInfoArrChange, onInfoArrPropertyChange, and onInfoArrLengthChange methods. 384 this.arrMonitor.infoArr = [new Info("Cindy", 8)]; 385 }) 386 Button("Push new info to infoArr") 387 .onClick(() => { 388 // Can trigger the onInfoArrPropertyChange and onInfoArrLengthChange methods. 389 this.arrMonitor.infoArr.push(new Info("David", 50)); 390 }) 391 } 392 } 393} 394``` 395 396- When the entire object changes but the listened property remains unchanged, the \@Monitor callback is not triggered. 397 398The following code represents the behavior in the comment when you execute the instructions in the sequence of Step 1, Step 2 and Step 3. 399 400If you only execute the instruction of Step 2 or Step 3 to change the values of **name** or **age**, the **onNameChange** and **onAgeChange** methods are triggered. 401 402```ts 403@ObservedV2 404class Info { 405 @Trace person: Person; 406 @Monitor("person.name") 407 onNameChange(monitor: IMonitor) { 408 console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 409 } 410 @Monitor("person.age") 411 onAgeChange(monitor: IMonitor) { 412 console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 413 } 414 constructor(name: string, age: number) { 415 this.person = new Person(name, age); 416 } 417} 418@ObservedV2 419class Person { 420 @Trace name: string; 421 @Trace age: number; 422 constructor(name: string, age: number) { 423 this.name = name; 424 this.age = age; 425 } 426} 427@Entry 428@ComponentV2 429struct Index { 430 info: Info = new Info("Tom", 25); 431 build() { 432 Column() { 433 Button("Step 1, only change name") 434 .onClick(() => { 435 this.info.person = new Person("Jack", 25); // Can trigger the onNameChange method, but not the onAgeChange method. 436 }) 437 Button("Step 2, only change age") 438 .onClick(() => { 439 this.info.person = new Person("Jack", 18); // Can trigger the onAgeChange method, but not the onNameChange method. 440 }) 441 Button("Step 3, change name and age") 442 .onClick(() => { 443 this.info.person = new Person("Lucy", 19); // Can trigger the onNameChange and onAgeChange methods. 444 }) 445 } 446 } 447} 448``` 449 450- If the property listened by \@Monitor is changed for multiple times in an event, the last change is used. 451 452```ts 453@ObservedV2 454class Frequence { 455 @Trace count: number = 0; 456 @Monitor("count") 457 onCountChange(monitor: IMonitor) { 458 console.log(`count change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 459 } 460} 461@Entry 462@ComponentV2 463struct Index { 464 frequence: Frequence = new Frequence(); 465 build() { 466 Column() { 467 Button("change count to 1000") 468 .onClick(() => { 469 for (let i = 1; i <= 1000; i++) { 470 this.frequence.count = i; 471 } 472 }) 473 Button("change count to 0 then to 1000") 474 .onClick(() => { 475 for (let i = 999; i >= 0; i--) { 476 this.frequence.count = i; 477 } 478 this.frequence.count = 1000; // Cannot trigger the onCountChange method at last. 479 }) 480 } 481 } 482} 483``` 484 485After you click **change count to 1000**, the **onCountChange** method is triggered and the **count change from 0 to 1000** log is output. After you click **change count to 0 then to 1000**, the **onCountChange** method is not triggered because the value of **count** property remains 1000 before and after the event. 486 487## Constraints 488 489Pay attention to the following constraints when using \@Monitor: 490 491- Do not listen for the same property for multiple times in a class. When a property in a class is listened for multiple times, only the last listening method takes effect. 492 493```ts 494@ObservedV2 495class Info { 496 @Trace name: string = "Tom"; 497 @Monitor("name") 498 onNameChange(monitor: IMonitor) { 499 console.log(`onNameChange`); 500 } 501 @Monitor("name") 502 onNameChangeDuplicate(monitor: IMonitor) { 503 console.log(`onNameChangeDuplicate`); 504 } 505} 506@Entry 507@ComponentV2 508struct Index { 509 info: Info = new Info(); 510 build() { 511 Column() { 512 Button("change name") 513 .onClick(() => { 514 this.info.name = "Jack"; // Only the onNameChangeDuplicate method is triggered. 515 }) 516 } 517 } 518} 519``` 520 521- The \@Monitor parameter must be a string that listens for the property name. Only string literals, **const** constants, and **enum** enumerated values can be used as parameters. If a variable is used as a parameter, only the property corresponding to the variable value during \@Monitor initialization is listened. When a variable is changed, \@Monitor cannot change the listened property in real time. That is, the target property listened by \@Monitor is determined during initialization and cannot be dynamically changed. Do not use variables as \@Monitor parameters for initialization. 522 523```ts 524const t2: string = "t2"; // Constant 525enum ENUM { 526 T3 = "t3" // Enum 527}; 528let t4: string = "t4"; // Variable 529@ObservedV2 530class Info { 531 @Trace t1: number = 0; 532 @Trace t2: number = 0; 533 @Trace t3: number = 0; 534 @Trace t4: number = 0; 535 @Trace t5: number = 0; 536 @Monitor("t1") // String literal 537 onT1Change(monitor: IMonitor) { 538 console.log(`t1 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 539 } 540 @Monitor(t2) 541 onT2Change(monitor: IMonitor) { 542 console.log(`t2 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 543 } 544 @Monitor(ENUM.T3) 545 onT3Change(monitor: IMonitor) { 546 console.log(`t3 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 547 } 548 @Monitor(t4) 549 onT4Change(monitor: IMonitor) { 550 console.log(`t4 change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 551 } 552} 553@Entry 554@ComponentV2 555struct Index { 556 info: Info = new Info(); 557 build() { 558 Column() { 559 Button("Change t1") 560 .onClick(() => { 561 this.info.t1++; // Can trigger the onT1Change method. 562 }) 563 Button("Change t2") 564 .onClick(() => { 565 this.info.t2++; // Can trigger the onT2Change method. 566 }) 567 Button("Change t3") 568 .onClick(() => { 569 this.info.t3++; // Can trigger the onT3Change method. 570 }) 571 Button("Change t4") 572 .onClick(() => { 573 this.info.t4++; // Can trigger the onT4Change method. 574 }) 575 Button("Change var t4 to t5") 576 .onClick(() => { 577 t4 = "t5"; // Change the variable value to "t5". 578 }) 579 Button("Change t5") 580 .onClick(() => { 581 this.info.t5++; // The onT4Change still listens for t4. Cannot trigger the method. 582 }) 583 Button("Change t4 again") 584 .onClick(() => { 585 this.info.t4++; // Can trigger the onT4Change method. 586 }) 587 } 588 } 589} 590``` 591 592- Changing the listened property in \@Monitor again may cause infinite loops, which is not recommended. 593 594```ts 595@ObservedV2 596class Info { 597 @Trace count: number = 0; 598 @Monitor("count") 599 onCountChange(monitor: IMonitor) { 600 this.count++; // Avoid using this method because it may cause infinite loops. 601 } 602} 603``` 604 605## Comparing \@Monitor with \@Watch 606 607The following table compares the usage and functions of \@Monitor and \@Watch. 608 609| | \@Watch | \@Monitor | 610| ------------------ | --------------------------------------- | ------------------------------------------------------------ | 611| Parameter | Call back the method name. | Listen for the state variable name and property name. | 612| Number of listened targets | Only a single state variable can be listened. | Multiple state variables can be listened at the same time. | 613| Listening capability | Listen for the top-level state variables. | Listen for the lower-level state variables. | 614| Obtain the value before change| No. | Yes. | 615| Listening Condition | The listened object is a state variable. | The listened object is a state variable or a class member property decorated by \@Trace. | 616| Constraints | It can be used only in custom components decorated by \@Component.| It can be used in custom components and classes decorated by \@ComponentV2.| 617 618## Use Scenarios 619 620### Listening for Lower-level Property Changes 621 622\@Monitor can listen for the lower-level property changes and classify them based on the values before and after the changes. 623 624In the following example, the change of property **value** is listened and the display style of the **Text** component is changed based on the change amplitude. 625 626```ts 627@ObservedV2 628class Info { 629 @Trace value: number = 50; 630} 631@ObservedV2 632class UIStyle { 633 info: Info = new Info(); 634 @Trace color: Color = Color.Black; 635 @Trace fontSize: number = 45; 636 @Monitor("info.value") 637 onValueChange(monitor: IMonitor) { 638 let lastValue: number = monitor.value()?.before as number; 639 let curValue: number = monitor.value()?.now as number; 640 if (lastValue != 0) { 641 let diffPercent: number = (curValue - lastValue) / lastValue; 642 if (diffPercent > 0.1) { 643 this.color = Color.Red; 644 this.fontSize = 50; 645 } else if (diffPercent < -0.1) { 646 this.color = Color.Green; 647 this.fontSize = 40; 648 } else { 649 this.color = Color.Black; 650 this.fontSize = 45; 651 } 652 } 653 } 654} 655@Entry 656@ComponentV2 657struct Index { 658 textStyle: UIStyle = new UIStyle(); 659 build() { 660 Column() { 661 Text(`Important Value: ${this.textStyle.info.value}`) 662 .fontColor(this.textStyle.color) 663 .fontSize(this.textStyle.fontSize) 664 Button("change!") 665 .onClick(() => { 666 this.textStyle.info.value = Math.floor(Math.random() * 100) + 1; 667 }) 668 } 669 } 670} 671``` 672 673## FAQs 674 675### Effective and Expiration Time of Variable Listening by the \@Monitor in the Custom Component 676 677When \@Monitor is defined in a custom component decorated by \@ComponentV2, \@Monitor takes effect after the state variable is initialized and becomes invalid when the component is destroyed. 678 679```ts 680@ObservedV2 681class Info { 682 @Trace message: string = "not initialized"; 683 684 constructor() { 685 console.log("in constructor message change to initialized"); 686 this.message = "initialized"; 687 } 688} 689@ComponentV2 690struct Child { 691 @Param info: Info = new Info(); 692 @Monitor("info.message") 693 onMessageChange(monitor: IMonitor) { 694 console.log(`Child message change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 695 } 696 aboutToAppear(): void { 697 this.info.message = "Child aboutToAppear"; 698 } 699 aboutToDisappear(): void { 700 console.log("Child aboutToDisappear"); 701 this.info.message = "Child aboutToDisappear"; 702 } 703 build() { 704 Column() { 705 Text("Child") 706 Button("change message in Child") 707 .onClick(() => { 708 this.info.message = "Child click to change Message"; 709 }) 710 } 711 .borderColor(Color.Red) 712 .borderWidth(2) 713 714 } 715} 716@Entry 717@ComponentV2 718struct Index { 719 @Local info: Info = new Info(); 720 @Local flag: boolean = false; 721 @Monitor("info.message") 722 onMessageChange(monitor: IMonitor) { 723 console.log(`Index message change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 724 } 725 726 build() { 727 Column() { 728 Button("show/hide Child") 729 .onClick(() => { 730 this.flag = !this.flag 731 }) 732 Button("change message in Index") 733 .onClick(() => { 734 this.info.message = "Index click to change Message"; 735 }) 736 if (this.flag) { 737 Child({ info: this.info }) 738 } 739 } 740 } 741} 742``` 743 744In the preceding example, you can create and destroy a **Child** component to observe the effective and expiration time of the \@Monitor defined in the custom component. You are advised to follow the steps below: 745 746- When the **Index** component creates an instance of the **Info** class, the log outputs the message: **in constructor message change to initialized**. At this time, the \@Monitor of the **Index** component has not been initialized successfully, so \@Monitor cannot listen for the message change. 747- After the **Index** component is created and the page is loaded, click **change message in Index** button. \@Monitor now can listen for the change and the log outputs the message "Index message change from initialized to Index click to change Message". 748- Click the **show/hide Child** button to create a **Child** component. After this component initializes the \@Param decorated variables and \@Monitor, call the **aboutToAppear** callback of the **Child** component to change the message. In this case, the \@Monitor of the **Index** and **Child** components can listen for the change, and the logs outputs the messages "Index message change from Index click to change Message to Child aboutToAppear" and "Child message change from Index click to change Message to Child aboutToAppear." 749- Click **change message in Child** button to change the message. In this case, the \@Monitor of the **Index** and **Child** components can listen for the change, and the log outputs the messages "Index message change from Child aboutToAppear to Child click to change Message" and "Child message change from Child aboutToAppear to Child click to change Message." 750- Click the **show/hide Child** button to destroy the **Child** component and call the **aboutToDisappear** callback to change the message. In this case, the \@Monitor of the **Index** and **Child** components can listen for the change, and the log outputs the messages "Child aboutToDisappear, Index message change from Child click to change Message to Child aboutToDisappear", and "Child message change from Child click to change Message to Child aboutToDisappear." 751- Click **change message in Index** button to change the message. In this case, the **Child** component is destroyed, and the \@Monitor is deregistered. Only the \@Monitor of the **Index** component can listen for the changes and the log outputs the message "Index message change from Child aboutToDisappear to Index click to change Message." 752 753The preceding steps indicate that the \@Monitor defined in the **Child** component takes effect when the **Child** component is created and initialized, and becomes invalid when the **Child** component is destroyed. 754 755### Effective and Expiration Time of Variable Listening by the \@Monitor in the Class 756 757When \@Monitor is defined in a class decorated by \@ComponentV2, \@Monitor takes effect after the class is created and becomes invalid when the class is destroyed. 758 759```ts 760@ObservedV2 761class Info { 762 @Trace message: string = "not initialized"; 763 764 constructor() { 765 this.message = "initialized"; 766 } 767 @Monitor("message") 768 onMessageChange(monitor: IMonitor) { 769 console.log(`message change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 770 } 771} 772 773@Entry 774@ComponentV2 775struct Index { 776 info: Info = new Info(); 777 778 aboutToAppear(): void { 779 this.info.message = "Index aboutToAppear"; 780 } 781 782 build() { 783 Column() { 784 Button("change message") 785 .onClick(() => { 786 this.info.message = "Index click to change message"; 787 }) 788 } 789 } 790} 791``` 792 793In the preceding example, \@Monitor takes effect after the **info** class is created, which is later than the **constructor** of the class and earlier than the **aboutToAppear** of the custom component. After the page is loaded, click **change message** button to modify the message variable. The log outputs the messages as below: 794 795```ts 796message change from initialized to Index aboutToAppear 797message change from Index aboutToAppear to Index click to change message 798``` 799 800\@Monitor defined in a class becomes invalid when the class is destroyed. However, the garbage collection mechanism determines whether a class is actually destroyed and released. Even if the custom component is destroyed, the class is not destroyed accordingly. As a result, the \@Monitor defined in the class still listens for changes. 801 802```ts 803@ObservedV2 804class InfoWrapper { 805 info?: Info; 806 constructor(info: Info) { 807 this.info = info; 808 } 809 @Monitor("info.age") 810 onInfoAgeChange(monitor: IMonitor) { 811 console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`) 812 } 813} 814@ObservedV2 815class Info { 816 @Trace age: number; 817 constructor(age: number) { 818 this.age = age; 819 } 820} 821@ComponentV2 822struct Child { 823 @Param @Require infoWrapper: InfoWrapper; 824 aboutToDisappear(): void { 825 console.log("Child aboutToDisappear", this.infoWrapper.info?.age) 826 } 827 build() { 828 Column() { 829 Text(`${this.infoWrapper.info?.age}`) 830 } 831 } 832} 833@Entry 834@ComponentV2 835struct Index { 836 dataArray: Info[] = []; 837 @Local showFlag: boolean = true; 838 aboutToAppear(): void { 839 for (let i = 0; i < 5; i++) { 840 this.dataArray.push(new Info(i)); 841 } 842 } 843 build() { 844 Column() { 845 Button("change showFlag") 846 .onClick(() => { 847 this.showFlag = !this.showFlag; 848 }) 849 Button("change number") 850 .onClick(() => { 851 console.log("click to change age") 852 this.dataArray.forEach((info: Info) => { 853 info.age += 100; 854 }) 855 }) 856 if (this.showFlag) { 857 Column() { 858 Text("Childs") 859 ForEach(this.dataArray, (info: Info) => { 860 Child({ infoWrapper: new InfoWrapper(info) }) 861 }) 862 } 863 .borderColor(Color.Red) 864 .borderWidth(2) 865 } 866 } 867 } 868} 869``` 870 871In the preceding example, when you click **change showFlag** to switch the condition of the **if** component, the **Child** component is destroyed. But when you click **change number** to change the value of **age**, the \@Monitor callback defined in **InfoWrapper** is still triggered. This is because the custom component **Child** has executed **aboutToDisappear**, but its member variable **infoWrapper** is not destroyed immediately. When the variable changes, the **onInfoAgeChange** method defined in **infoWrapper** can still be called, therefore, the \@Monitor callback is still triggered. 872 873The result is unstable when you use the garbage collection mechanism to cancel the listening of \@Monitor. You can use either of the following methods to manage the expiration time of the \@Monitor: 874 8751. Define \@Monitor in the custom component. When a custom component is destroyed, the state management framework cancels the listening of \@Monitor. Therefore, after the custom component calls **aboutToDisappear**, the \@Monitor callback will not be triggered even though the data of the custom component may not be released. 876 877```ts 878@ObservedV2 879class InfoWrapper { 880 info?: Info; 881 constructor(info: Info) { 882 this.info = info; 883 } 884} 885@ObservedV2 886class Info { 887 @Trace age: number; 888 constructor(age: number) { 889 this.age = age; 890 } 891} 892@ComponentV2 893struct Child { 894 @Param @Require infoWrapper: InfoWrapper; 895 @Monitor("infoWrapper.info.age") 896 onInfoAgeChange(monitor: IMonitor) { 897 console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`) 898 } 899 aboutToDisappear(): void { 900 console.log("Child aboutToDisappear", this.infoWrapper.info?.age) 901 } 902 build() { 903 Column() { 904 Text(`${this.infoWrapper.info?.age}`) 905 } 906 } 907} 908@Entry 909@ComponentV2 910struct Index { 911 dataArray: Info[] = []; 912 @Local showFlag: boolean = true; 913 aboutToAppear(): void { 914 for (let i = 0; i < 5; i++) { 915 this.dataArray.push(new Info(i)); 916 } 917 } 918 build() { 919 Column() { 920 Button("change showFlag") 921 .onClick(() => { 922 this.showFlag = !this.showFlag; 923 }) 924 Button("change number") 925 .onClick(() => { 926 console.log("click to change age") 927 this.dataArray.forEach((info: Info) => { 928 info.age += 100; 929 }) 930 }) 931 if (this.showFlag) { 932 Column() { 933 Text("Childs") 934 ForEach(this.dataArray, (info: Info) => { 935 Child({ infoWrapper: new InfoWrapper(info) }) 936 }) 937 } 938 .borderColor(Color.Red) 939 .borderWidth(2) 940 } 941 } 942 } 943} 944``` 945 9462. Set the listened object to empty. When the custom component is about to be destroyed, the \@Monitor listened object is set empty. In this way, the \@Monitor cannot listen for the changes of the original object, so that the listening is cancelled. 947 948```ts 949@ObservedV2 950class InfoWrapper { 951 info?: Info; 952 constructor(info: Info) { 953 this.info = info; 954 } 955 @Monitor("info.age") 956 onInfoAgeChange(monitor: IMonitor) { 957 console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`) 958 } 959} 960@ObservedV2 961class Info { 962 @Trace age: number; 963 constructor(age: number) { 964 this.age = age; 965 } 966} 967@ComponentV2 968struct Child { 969 @Param @Require infoWrapper: InfoWrapper; 970 aboutToDisappear(): void { 971 console.log("Child aboutToDisappear", this.infoWrapper.info?.age) 972 this.infoWrapper.info = undefined; // Disable the InfoWrapper from listening for info.age. 973 } 974 build() { 975 Column() { 976 Text(`${this.infoWrapper.info?.age}`) 977 } 978 } 979} 980@Entry 981@ComponentV2 982struct Index { 983 dataArray: Info[] = []; 984 @Local showFlag: boolean = true; 985 aboutToAppear(): void { 986 for (let i = 0; i < 5; i++) { 987 this.dataArray.push(new Info(i)); 988 } 989 } 990 build() { 991 Column() { 992 Button("change showFlag") 993 .onClick(() => { 994 this.showFlag = !this.showFlag; 995 }) 996 Button("change number") 997 .onClick(() => { 998 console.log("click to change age") 999 this.dataArray.forEach((info: Info) => { 1000 info.age += 100; 1001 }) 1002 }) 1003 if (this.showFlag) { 1004 Column() { 1005 Text("Childs") 1006 ForEach(this.dataArray, (info: Info) => { 1007 Child({ infoWrapper: new InfoWrapper(info) }) 1008 }) 1009 } 1010 .borderColor(Color.Red) 1011 .borderWidth(2) 1012 } 1013 } 1014 } 1015} 1016``` 1017 1018### Passing Correct Input Parameters to \@Monitor 1019 1020\@Monitor cannot verify input parameters during compilation. Currently, the following statements do not meet the listening condition, but \@Monitor is still triggered. Therefore, you should correctly pass the input parameter and do not pass non-state variables. Otherwise, function exceptions or unexpected behavior may occur. 1021 1022[Negative example 1] 1023 1024```ts 1025@ObservedV2 1026class Info { 1027 name: string = "John"; 1028 @Trace age: number = 24; 1029 @Monitor("age", "name") // Listen for the state variable "age" and non-state variable "name" at the same time. 1030 onPropertyChange(monitor: IMonitor) { 1031 monitor.dirty.forEach((path: string) => { 1032 console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 1033 }) 1034 } 1035} 1036@Entry 1037@ComponentV2 1038struct Index { 1039 info: Info = new Info(); 1040 build() { 1041 Column() { 1042 Button("change age&name") 1043 .onClick(() => { 1044 this.info.age = 25; // Change the state variable "age" and non-state variable "name" at the same time. 1045 this.info.name = "Johny"; 1046 }) 1047 } 1048 } 1049} 1050``` 1051 1052In the preceding code, when state variable **age** and non-state variable **name** are changed at the same time, the following log is generated: 1053 1054``` 1055property path:age change from 24 to 25 1056property path:name change from John to Johny 1057``` 1058 1059Actually, the **name** attribute is not an observable variable and should not be added to the input parameters of \@Monitor. You are advised to remove the listening from the **name** attribute or add \@Trace to decorate the **name** attribute as a state variable. 1060 1061[Correct example 1] 1062 1063```ts 1064@ObservedV2 1065class Info { 1066 name: string = "John"; 1067 @Trace age: number = 24; 1068 @Monitor("age") // Only listen for the state variable "age". 1069 onPropertyChange(monitor: IMonitor) { 1070 monitor.dirty.forEach((path: string) => { 1071 console.log(`property path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`); 1072 }) 1073 } 1074} 1075@Entry 1076@ComponentV2 1077struct Index { 1078 info: Info = new Info(); 1079 build() { 1080 Column() { 1081 Button("change age&name") 1082 .onClick(() => { 1083 this.info.age = 25; // The state variable "age" is changed. 1084 this.info.name = "Johny"; 1085 }) 1086 } 1087 } 1088} 1089``` 1090 1091[Negative example 2] 1092 1093```ts 1094@ObservedV2 1095class Info { 1096 name: string = "John"; 1097 @Trace age: number = 24; 1098 get myAge() { 1099 return this.age; // age is a state variable. 1100 } 1101 @Monitor("myAge") // Listen for non-@Computed decorated getter accessor. 1102 onPropertyChange() { 1103 console.log("age changed"); 1104 } 1105} 1106@Entry 1107@ComponentV2 1108struct Index { 1109 info: Info = new Info(); 1110 build() { 1111 Column() { 1112 Button("change age") 1113 .onClick(() => { 1114 this.info.age = 25; // The state variable "age" is changed. 1115 }) 1116 } 1117 } 1118} 1119``` 1120 1121In the preceding code, the input parameter of \@Monitor is the name of a **getter** accessor. This accessor is not decorated by \@Computed and is not a variable that can be listened for. However, the state variable is used for computation. After the state variable changes, **myAge** is changed, invoking the \@Monitor callback. You are advised to add an \@Computed decorator to **myAge** or directly listen for the state variable itself when the **getter** accessor returns the state variable. 1122 1123[Positive example 2] 1124 1125Change **myAge** to a state variable: 1126 1127```ts 1128@ObservedV2 1129class Info { 1130 name: string = "John"; 1131 @Trace age: number = 24; 1132 @Computed // Add @Computed to myAge as a state variable. 1133 get myAge() { 1134 return this.age; 1135 } 1136 @Monitor("myAge") // Listen for @Computed decorated getter accessor. 1137 onPropertyChange() { 1138 console.log("age changed"); 1139 } 1140} 1141@Entry 1142@ComponentV2 1143struct Index { 1144 info: Info = new Info(); 1145 build() { 1146 Column() { 1147 Button("change age") 1148 .onClick(() => { 1149 this.info.age = 25; // The state variable "age" is changed. 1150 }) 1151 } 1152 } 1153} 1154``` 1155 1156Alternatively, listen to the state variable itself. 1157 1158```ts 1159@ObservedV2 1160class Info { 1161 name: string = "John"; 1162 @Trace age: number = 24; 1163 @Monitor("age") // Only listen for the state variable "age". 1164 onPropertyChange() { 1165 console.log("age changed"); 1166 } 1167} 1168@Entry 1169@ComponentV2 1170struct Index { 1171 info: Info = new Info(); 1172 build() { 1173 Column() { 1174 Button("change age") 1175 .onClick(() => { 1176 this.info.age = 25; // The state variable "age" is changed. 1177 }) 1178 } 1179 } 1180} 1181``` 1182