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