1# Freezing a Custom Component 2 3When a custom component decorated by @ComponentV2 is inactive, it can be frozen so that its state variable does not respond to updates. That is, the @Monitor decorated method is not called, and the node associated with the state variable is not re-rendered. You can use the **freezeWhenInactive** attribute to specify whether to freeze a custom component. If no parameter is passed in, the feature is disabled. This feature works in following scenarios: page routing, **TabContent**, and **Navigation**. 4 5Before reading this topic, you are advised to read [\@ComponentV2](./arkts-new-componentV2.md). 6 7> **NOTE** 8> 9> Freezing of @ComponentV2 decorated custom component is supported since API version 12. 10> 11> Mixed use of custom component freezing is supported since API version 18. 12> 13> Different from freezing the @Component decorated components, custom components decorated by @ComponentV2 do not support freezing the cached list items in the **LazyForEach** scenario. 14 15 16## Use Scenarios 17 18### Page Routing 19 20> **NOTE** 21> 22> This example uses router for page redirection but you are advised to use the **Navigation** component instead, because **Navigation** provides more functions and more flexible customization capabilities. For details, see the use cases of [Navigation](#navigation). 23 24- When page 1 calls the **router.pushUrl** API to jump to page 2, page 1 is hidden and invisible. In this case, if the state variable on page 1 is updated, page 1 is not re-rendered. 25For details, see the following. 26 27 28 29Page 1 30 31```ts 32import { router } from '@kit.ArkUI'; 33 34@ObservedV2 35export class Book { 36 @Trace name: string = "100"; 37 38 constructor(page: string) { 39 this.name = page; 40 } 41} 42 43@Entry 44@ComponentV2({ freezeWhenInactive: true }) 45export struct Page1 { 46 @Local bookTest: Book = new Book("A Midsummer Night's Dream"); 47 48 @Monitor("bookTest.name") 49 onMessageChange(monitor: IMonitor) { 50 console.log(`The book name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 51 } 52 53 build() { 54 Column() { 55 Text(`Book name is ${this.bookTest.name}`).fontSize(25) 56 Button('changeBookName').fontSize(25) 57 .onClick(() => { 58 this.bookTest.name = "The Old Man and the Sea"; 59 }) 60 Button('go to next page').fontSize(25) 61 .onClick(() => { 62 router.pushUrl({ url: 'pages/Page2' }); 63 setTimeout(() => { 64 this.bookTest = new Book("Jane Austen oPride and Prejudice"); 65 }, 1000) 66 }) 67 } 68 } 69} 70``` 71 72Page 2 73 74```ts 75import { router } from '@kit.ArkUI'; 76 77@Entry 78@ComponentV2 79struct Page2 { 80 build() { 81 Column() { 82 Text(`This is the page2`).fontSize(25) 83 Button('Back') 84 .onClick(() => { 85 router.back(); 86 }) 87 } 88 } 89} 90``` 91 92In the preceding example: 93 941. Click the **changeBookName** button on page 1. The **name** attribute of the **bookTest** variable is changed, and the **onMessageChange** method registered in @Monitor is called. 95 962. Click the **go to next page** button on page 1 to redirect to page 2, and then update the state variable **bookTest** 1s later. When **bookTest** is updated, page 2 is displayed and Page 1 is in the inactive state. The state variable @Local bookTest does not respond to the update. Therefore, the @Monitor is not called, and the node associated with the state variable is not updated. 97The trace diagram is as follows. 98 99 100 101 1023. Click **Back**. Page 2 is destroyed, and the state of page 1 changes from inactive to active. The update of the state variable **bookTest** is observed, the **onMessageChange** method registered in @Monitor is called, and the corresponding text content is changed. 103 104 105 106 107### TabContent 108 109- You can freeze invisible **TabContent** components in the **Tabs** container so that they do not trigger UI re-rendering. 110 111- During initial rendering, only the **TabContent** component that is being displayed is created. All **TabContent** components are created only after all of them have been switched to. 112 113For details, see the following. 114 115 116 117```ts 118@Entry 119@ComponentV2 120struct TabContentTest { 121 @Local message: number = 0; 122 @Local data: number[] = [0, 1]; 123 124 build() { 125 Row() { 126 Column() { 127 Button('change message').onClick(() => { 128 this.message++; 129 }) 130 131 Tabs() { 132 ForEach(this.data, (item: number) => { 133 TabContent() { 134 FreezeChild({ message: this.message, index: item }) 135 }.tabBar(`tab${item}`) 136 }, (item: number) => item.toString()) 137 } 138 } 139 .width('100%') 140 } 141 .height('100%') 142 } 143} 144 145@ComponentV2({ freezeWhenInactive: true }) 146struct FreezeChild { 147 @Param message: number = 0; 148 @Param index: number = 0; 149 150 @Monitor('message') onMessageUpdated(mon: IMonitor) { 151 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`); 152 } 153 154 build() { 155 Text("message" + `${this.message}, index: ${this.index}`) 156 .fontSize(50) 157 .fontWeight(FontWeight.Bold) 158 } 159} 160``` 161 162In the preceding example: 163 1641. When **change message** is clicked, the value of **message** changes, and the @Monitor decorated **onMessageUpdated** method of the **TabContent** component being displayed is called. 165 1662. When **tab1** in **TabBar** is clicked to switch to another **TabContent** component, the component switches from inactive to active, and the corresponding @Monitor decorated **onMessageUpdated** method is called. 167 1683. When **change message** is clicked again, the value of **message** changes, and only the @Monitor decorated **onMessageUpdated** method of the **TabContent** component being displayed is called. Other inactive **TabContent** components do not trigger @Monitor. 169 170 171 172 173### Navigation 174 175- When the navigation destination page is invisible, its child custom components are set to the inactive state and will not be re-rendered. When return to this page, its child custom components are restored to the active state and the @Monitor callback is triggered to re-render the page. 176 177```ts 178@Entry 179@ComponentV2 180struct MyNavigationTestStack { 181 @Provider('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 182 @Local message: number = 0; 183 184 @Monitor('message') info() { 185 console.info(`freeze-test MyNavigation message callback ${this.message}`); 186 } 187 188 @Builder 189 PageMap(name: string) { 190 if (name === 'pageOne') { 191 pageOneStack({ message: this.message }) 192 } else if (name === 'pageTwo') { 193 pageTwoStack({ message: this.message }) 194 } else if (name === 'pageThree') { 195 pageThreeStack({ message: this.message }) 196 } 197 } 198 199 build() { 200 Column() { 201 Button('change message') 202 .onClick(() => { 203 this.message++; 204 }) 205 Navigation(this.pageInfo) { 206 Column() { 207 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 208 .onClick(() => { 209 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 210 }) 211 } 212 }.title('NavIndex') 213 .navDestination(this.PageMap) 214 .mode(NavigationMode.Stack) 215 } 216 } 217} 218 219@ComponentV2 220struct pageOneStack { 221 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 222 @Local index: number = 1; 223 @Param message: number = 0; 224 225 build() { 226 NavDestination() { 227 Column() { 228 NavigationContentMsgStack({ message: this.message, index: this.index }) 229 Text("cur stack size:" + `${this.pageInfo.size()}`) 230 .fontSize(30) 231 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 232 .onClick(() => { 233 this.pageInfo.pushPathByName('pageTwo', null); 234 }) 235 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 236 .onClick(() => { 237 this.pageInfo.pop(); 238 }) 239 }.width('100%').height('100%') 240 }.title('pageOne') 241 .onBackPressed(() => { 242 this.pageInfo.pop(); 243 return true; 244 }) 245 } 246} 247 248@ComponentV2 249struct pageTwoStack { 250 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 251 @Local index: number = 2; 252 @Param message: number = 0; 253 254 build() { 255 NavDestination() { 256 Column() { 257 NavigationContentMsgStack({ message: this.message, index: this.index }) 258 Text("cur stack size:" + `${this.pageInfo.size()}`) 259 .fontSize(30) 260 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 261 .onClick(() => { 262 this.pageInfo.pushPathByName('pageThree', null); 263 }) 264 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 265 .onClick(() => { 266 this.pageInfo.pop(); 267 }) 268 } 269 }.title('pageTwo') 270 .onBackPressed(() => { 271 this.pageInfo.pop(); 272 return true; 273 }) 274 } 275} 276 277@ComponentV2 278struct pageThreeStack { 279 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 280 @Local index: number = 3; 281 @Param message: number = 0; 282 283 build() { 284 NavDestination() { 285 Column() { 286 NavigationContentMsgStack({ message: this.message, index: this.index }) 287 Text("cur stack size:" + `${this.pageInfo.size()}`) 288 .fontSize(30) 289 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 290 .height(40) 291 .onClick(() => { 292 this.pageInfo.pushPathByName('pageOne', null); 293 }) 294 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 295 .height(40) 296 .onClick(() => { 297 this.pageInfo.pop(); 298 }) 299 } 300 }.title('pageThree') 301 .onBackPressed(() => { 302 this.pageInfo.pop(); 303 return true; 304 }) 305 } 306} 307 308@ComponentV2({ freezeWhenInactive: true }) 309struct NavigationContentMsgStack { 310 @Param message: number = 0; 311 @Param index: number = 0; 312 313 @Monitor('message') info() { 314 console.info(`freeze-test NavigationContent message callback ${this.message}`); 315 console.info(`freeze-test ---- called by content ${this.index}`); 316 } 317 318 build() { 319 Column() { 320 Text("msg:" + `${this.message}`) 321 .fontSize(30) 322 } 323 } 324} 325``` 326 327In the preceding example: 328 3291. When **change message** is clicked, the value of **message** changes, and the @Monitor decorated **info** method of the **MyNavigationTestStack** component being displayed is called. 330 3312. When **Next Page** is clicked, the page is switched to **PageOne** and the **pageOneStack** node is created. 332 3333. When **change message** is clicked again, the value of **message** changes, and only the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component in **pageOneStack** is called. 334 3354. When **Next Page** is clicked, the page is switched to **PageTwo** and the **pageTwoStack** node is created. The state of the **pageOneStack** node changes from active to inactive. 336 3375. When **change message** is clicked again, the value of **message** changes, and only the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component in **pageTwoStack** is called. The child custom component in **NavDestination** that is not at the top of the navigation routing stack is in the inactive state. The @Monitor method is not triggered. 338 3396. When **Next Page** is clicked, the page is switched to **PageThree** and the **pageThreeStack** node is created. The state of the **pageTwoStack** node changes from active to inactive. 340 3417. When **change message** is clicked again, the value of **message** changes, and only the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component in **pageThreeStack** is called. The child custom component in **NavDestination** that is not at the top of the navigation routing stack is in the inactive state. The @Monitor method is not triggered. 342 3438. Click **Back Page** to return to **PageTwo**. The state of the **pageTwoStack** node changes from inactive to active, and the **info** method registered in @Monitor of the **NavigationContentMsgStack** child component is triggered. 344 3459. Click **Back Page** again to return to **PageOne**. The state of the **pageOneStack** node changes from inactive to active, and the **info** method registered in @Monitor of the **NavigationContentMsgStack** child component is triggered. 346 34710. When **Back Page** is clicked, the page is switched to the initial page. 348 349 350 351### Repeat virtualScroll 352 353> **NOTE** 354> 355> Repeat virtualScroll supports custom component freezing since API version 18. 356 357Freeze the custom components in the Repeat virtualScroll cache pool to avoid unnecessary component re-renders. You are advised to read [Child Component Rendering Logic](./arkts-new-rendering-control-repeat.md#child-component-rendering-logic-1) of virtualScroll in advance. 358 359```ts 360@Entry 361@ComponentV2 362struct RepeatVirtualScrollFreeze { 363 @Local simpleList: Array<string> = []; 364 @Local bgColor: Color = Color.Pink; 365 366 aboutToAppear(): void { 367 for (let i = 0; i < 7; i++) { 368 this.simpleList.push(`item${i}`); 369 } 370 } 371 372 build() { 373 Column() { 374 Row() { 375 Button(`Reduce length to 5`) 376 .onClick(() => { 377 this.simpleList = this.simpleList.slice(0, 5); 378 }) 379 Button(`Change bgColor`) 380 .onClick(() => { 381 this.bgColor = this.bgColor == Color.Pink ? Color.Blue : Color.Pink; 382 }) 383 } 384 385 List() { 386 Repeat(this.simpleList) 387 .each((obj: RepeatItem<string>) => { 388 }) 389 .key((item: string, index: number) => item) 390 .virtualScroll({ totalCount: this.simpleList.length }) 391 .templateId(() => `a`) 392 .template(`a`, (ri) => { 393 ChildComponent({ 394 message: ri.item, 395 bgColor: this.bgColor 396 }) 397 }, { cachedCount: 2 }) 398 } 399 .cachedCount(0) 400 .height(500) 401 } 402 .height(`100%`) 403 } 404} 405 406// Enable component freezing. 407@ComponentV2({ freezeWhenInactive: true }) 408struct ChildComponent { 409 @Param @Require message: string = ``; 410 @Param @Require bgColor: Color = Color.Pink; 411 @Monitor(`bgColor`) 412 onBgColorChange(monitor: IMonitor) { 413 // When the bgColor changes, the components in the cache pool are not re-rendered and no log is printed. 414 console.log(`repeat---bgColor change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 415 } 416 417 build() { 418 Text(`[a]: ${this.message}`) 419 .fontSize(50) 420 .backgroundColor(this.bgColor) 421 } 422} 423``` 424 425In the preceding example: 426 427After you click **Reduce length to 5**, the two removed components enter the **Repeat** cache pool. Then, click **Change bgColor** to change the value of **bgColor** to trigger node re-rendering. 428 429If **freezeWhenInactive** is set to **true**, only the **onBgColorChange** method decorated by @Monitor in the remaining nodes is triggered. In the example, the five nodes are re-rendered and five logs are printed. The nodes in the cache pool are not re-rendered. 430 431 432 433```ts 434// Disable component freezing. 435@ComponentV2({ freezeWhenInactive: false }) 436struct ChildComponent { 437 @Param @Require message: string = ``; 438 @Param @Require bgColor: Color = Color.Pink; 439 @Monitor(`bgColor`) 440 onBgColorChange(monitor: IMonitor) { 441 // When the bgColor changes, components in the cache pool are re-rendered and logs are printed. 442 console.log(`repeat---bgColor change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 443 } 444 445 build() { 446 Text(`[a]: ${this.message}`) 447 .fontSize(50) 448 .backgroundColor(this.bgColor) 449 } 450} 451``` 452 453**freezeWhenInactive** is set to **false** to disable component freezing. If **freezeWhenInactive** is not specified, component freezing is disabled by default. The **onBgColorChange** method decorated by @Monitor in the remaining nodes and cache pool nodes is triggered, that is, seven nodes are re-rendered and seven logs are printed. 454 455 456 457### Mixed Use of Component Freezing 458 459In the scenario where mixed use of component freezing is supported, the freezing behavior varies according to the API version. Set the component freezing flag for the parent component. In API version 17 or earlier, when the parent component is unfrozen, all nodes of its child components are unfrozen. Since API version 18, when the parent component is unfrozen, only the on-screen nodes of the child component are unfrozen. For details, see [Mixing the Use of Components](./arkts-custom-components-freeze.md#mixing-the-use-of-components). 460 461#### Mixing Use of Navigation and TabContent 462 463```ts 464@ComponentV2 465struct ChildOfParamComponent { 466 @Require @Param child_val: number; 467 468 @Monitor('child_val') onChange(m: IMonitor) { 469 console.log(`Appmonitor ChildOfParamComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 470 } 471 472 build() { 473 Column() { 474 Text(`Child Param: ${this.child_val}`); 475 } 476 } 477} 478 479@ComponentV2 480struct ParamComponent { 481 @Require @Param val: number; 482 483 @Monitor('val') onChange(m: IMonitor) { 484 console.log(`Appmonitor ParamComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 485 } 486 487 build() { 488 Column() { 489 Text(`val: ${this.val}`); 490 ChildOfParamComponent({child_val: this.val}); 491 } 492 } 493} 494 495@ComponentV2 496struct DelayComponent { 497 @Require @Param delayVal1: number; 498 499 @Monitor('delayVal1') onChange(m: IMonitor) { 500 console.log(`Appmonitor DelayComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 501 } 502 503 build() { 504 Column() { 505 Text(`Delay Param: ${this.delayVal1}`); 506 } 507 } 508} 509 510@ComponentV2 ({freezeWhenInactive: true}) 511struct TabsComponent { 512 private controller: TabsController = new TabsController(); 513 @Local tabState: number = 47; 514 515 @Monitor('tabState') onChange(m: IMonitor) { 516 console.log(`Appmonitor TabsComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 517 } 518 519 build() { 520 Column({space: 10}) { 521 Button(`Incr state ${this.tabState}`) 522 .fontSize(25) 523 .onClick(() => { 524 console.log('Button increment state value'); 525 this.tabState = this.tabState + 1; 526 }) 527 528 Tabs({ barPosition: BarPosition.Start, index: 0, controller: this.controller}) { 529 TabContent() { 530 ParamComponent({val: this.tabState}); 531 }.tabBar('Update') 532 TabContent() { 533 DelayComponent({delayVal1: this.tabState}); 534 }.tabBar('DelayUpdate') 535 } 536 .vertical(false) 537 .scrollable(true) 538 .barMode(BarMode.Fixed) 539 .barWidth(400).barHeight(150).animationDuration(400) 540 .width('100%') 541 .height(200) 542 .backgroundColor(0xF5F5F5) 543 } 544 } 545} 546 547@Entry 548@Component 549struct MyNavigationTestStack { 550 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 551 552 @Builder 553 PageMap(name: string) { 554 if (name === 'pageOne') { 555 pageOneStack() 556 } else if (name === 'pageTwo') { 557 pageTwoStack() 558 } 559 } 560 561 build() { 562 Column() { 563 Navigation(this.pageInfo) { 564 Column() { 565 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 566 .width('80%') 567 .height(40) 568 .margin(20) 569 .onClick(() => { 570 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 571 }) 572 } 573 }.title('NavIndex') 574 .navDestination(this.PageMap) 575 .mode(NavigationMode.Stack) 576 } 577 } 578} 579 580@Component 581struct pageOneStack { 582 @Consume('pageInfo') pageInfo: NavPathStack; 583 584 build() { 585 NavDestination() { 586 Column() { 587 TabsComponent(); 588 589 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 590 .width('80%') 591 .height(40) 592 .margin(20) 593 .onClick(() => { 594 this.pageInfo.pushPathByName('pageTwo', null); 595 }) 596 }.width('100%').height('100%') 597 }.title('pageOne') 598 .onBackPressed(() => { 599 this.pageInfo.pop(); 600 return true; 601 }) 602 } 603} 604 605@Component 606struct pageTwoStack { 607 @Consume('pageInfo') pageInfo: NavPathStack; 608 609 build() { 610 NavDestination() { 611 Column() { 612 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 613 .width('80%') 614 .height(40) 615 .margin(20) 616 .onClick(() => { 617 this.pageInfo.pop(); 618 }) 619 }.width('100%').height('100%') 620 }.title('pageTwo') 621 .onBackPressed(() => { 622 this.pageInfo.pop(); 623 return true; 624 }) 625 } 626} 627``` 628 629For API version 17 or earlier: 630 631Click **Next page** to enter the next page and then return to the previous page. All labels of **Tabcontent** are unfrozen. 632 633For API version 18 or later: 634 635Click **Next page** to enter the next page and then return to the previous page. Only the nodes with the corresponding labels are unfrozen. 636 637## Constraints 638 639As shown in the following example, the custom node [BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md) is used in **FreezeBuildNode**. **BuilderNode** can dynamically mount components using commands and component freezing strongly depends on the parent-child relationship to determine whether it is enabled. In this case, if the parent component is frozen and **BuilderNode** is enabled at the middle level of the component tree, the child component of the **BuilderNode** cannot be frozen. 640 641``` 642import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 643 644// Define a Params class to pass parameters. 645@ObservedV2 646class Params { 647 // Singleton mode. Ensure that there is only one Params instance. 648 static singleton_: Params; 649 650 // Method for obtaining the Params instance. 651 static instance() { 652 if (!Params.singleton_) { 653 Params.singleton_ = new Params(0); 654 } 655 return Params.singleton_; 656 } 657 658 // Use the @Trace decorator to decorate the message attribute so that its changes are observable. 659 @Trace message: string = "Hello"; 660 index: number = 0; 661 662 constructor(index: number) { 663 this.index = index; 664 } 665} 666 667// Define a buildNodeChild component that contains a message attribute and an index attribute. 668@ComponentV2 669struct buildNodeChild { 670 // Use the Params instance as the storage attribute. 671 storage: Params = Params.instance(); 672 @Param index: number = 0; 673 674 // Use the @Monitor decorator to listen for the changes of storage.message. 675 @Monitor("storage.message") 676 onMessageChange(monitor: IMonitor) { 677 console.log(`FreezeBuildNode buildNodeChild message callback func ${this.storage.message}, index:${this.index}`); 678 } 679 680 build() { 681 Text(`buildNode Child message: ${this.storage.message}`).fontSize(30) 682 } 683} 684 685// Define a buildText function that receives a Params parameter and constructs a Column component. 686@Builder 687function buildText(params: Params) { 688 Column() { 689 buildNodeChild({ index: params.index }) 690 } 691} 692 693class TextNodeController extends NodeController { 694 private textNode: BuilderNode<[Params]> | null = null; 695 private index: number = 0; 696 697 // The constructor receives an index parameter. 698 constructor(index: number) { 699 super(); 700 this.index = index; 701 } 702 703 // Create and return a FrameNode. 704 makeNode(context: UIContext): FrameNode | null { 705 this.textNode = new BuilderNode(context); 706 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.index)); 707 return this.textNode.getFrameNode(); 708 } 709} 710 711// Define an index component that contains a message attribute and a data array. 712@Entry 713@ComponentV2 714struct Index { 715 // Use the Params instance as the storage attribute. 716 storage: Params = Params.instance(); 717 private data: number[] = [0, 1]; 718 719 build() { 720 Row() { 721 Column() { 722 Button("change").fontSize(30) 723 .onClick(() => { 724 this.storage.message += 'a'; 725 }) 726 727 Tabs() { 728 // Use Repeat to repeatedly render the TabContent component. 729 Repeat<number>(this.data) 730 .each((obj: RepeatItem<number>) => { 731 TabContent() { 732 FreezeBuildNode({ index: obj.item }) 733 .margin({ top: 20 }) 734 }.tabBar(`tab${obj.item}`) 735 }) 736 .key((item: number) => item.toString()) 737 } 738 } 739 } 740 .width('100%') 741 .height('100%') 742 } 743} 744 745// Define a FreezeBuildNode component that contains a message attribute and an index attribute. 746@ComponentV2({ freezeWhenInactive: true }) 747struct FreezeBuildNode { 748 // Use the Params instance as the storage attribute. 749 storage: Params = Params.instance(); 750 @Param index: number = 0; 751 752 // Use the @Monitor decorator to listen for the changes of storage.message. 753 @Monitor("storage.message") 754 onMessageChange(monitor: IMonitor) { 755 console.log(`FreezeBuildNode message callback func ${this.storage.message}, index: ${this.index}`); 756 } 757 758 build() { 759 NodeContainer(new TextNodeController(this.index)) 760 .width('100%') 761 .height('100%') 762 .backgroundColor('#FFF0F0F0') 763 } 764} 765``` 766 767Click **Button("change")** to change the value of **message**. The **onMessageUpdated** method registered in @Watch of the **TabContent** component that is being displayed is triggered, and that under the **BuilderNode** node of **TabContent** that is not displayed is also triggered. 768 769 770 771<!--no_check-->