1# Freezing a Custom Component 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @liwenzhen3--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9When a custom component decorated by @ComponentV2 is inactive, it can be frozen so that its state variables do not respond to updates. This means that the [@Monitor](./arkts-new-monitor.md) decorated callback will not be triggered, and any nodes associated with these state variables will not be re-rendered. This freezing mechanism offers significant performance benefits in complex UI scenarios. It prevents inactive components from performing unnecessary updates when their state variables update, thereby reducing resource consumption. You can use the **freezeWhenInactive** attribute to specify whether to enable the freezing feature. If no parameter is passed in, this feature is disabled. The freezing feature is supported in the following scenarios and components: [page navigation and routing](../../reference/apis-arkui/js-apis-router.md), [TabContent](../../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md), [Navigation](../../reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md), [Repeat](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md). 10 11To implement this feature, a solid understanding of the basic syntax for @ComponentV2 is required. Therefore, you are advised to read the [\@ComponentV2 documentation](./arkts-new-componentV2.md) before proceeding. 12 13> **NOTE** 14> 15> Freezing of @ComponentV2 decorated custom components is supported since API version 12. 16> 17> Custom component freezing across mixed scenarios is supported since API version 18. 18> 19> Unlike @Component decorated components, @ComponentV2 decorated components do not support freezing cached items in the **LazyForEach** scenario. 20 21## Use Scenarios 22 23### Page Navigation and Routing 24 25> **NOTE** 26> 27> While this example demonstrates page navigation and routing using **router** APIs, you are advised to use the **Navigation** component instead, which offers enhanced functionality and greater customization flexibility. For details, see the use cases of [Navigation](#navigation). 28 29- When page 1 navigates to page 2 using **router.pushUrl**, it enters the hidden state, where updating its state variables will not trigger UI re-rendering. 30The figure below shows these pages. 31 32 33 34Page 1 implementation: 35 36```ts 37@ObservedV2 38export class Book { 39 @Trace name: string = "100"; 40 41 constructor(page: string) { 42 this.name = page; 43 } 44} 45 46@Entry 47@ComponentV2({ freezeWhenInactive: true }) 48export struct Page1 { 49 @Local bookTest: Book = new Book("A Midsummer Night's Dream"); 50 51 @Monitor("bookTest.name") 52 onMessageChange(monitor: IMonitor) { 53 console.info(`The book name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 54 } 55 56 build() { 57 Column() { 58 Text(`Book name is ${this.bookTest.name}`).fontSize(25) 59 Button('changeBookName').fontSize(25) 60 .onClick(() => { 61 this.bookTest.name = "The Old Man and the Sea"; 62 }) 63 Button('go to next page').fontSize(25) 64 .onClick(() => { 65 this.getUIContext().getRouter().pushUrl({ url: 'pages/Page2' }); 66 setTimeout(() => { 67 this.bookTest = new Book("Jane Austen's Pride and Prejudice"); 68 }, 1000) 69 }) 70 } 71 } 72} 73``` 74 75Page 2 implementation: 76 77```ts 78@Entry 79@ComponentV2 80struct Page2 { 81 build() { 82 Column() { 83 Text(`This is the page2`).fontSize(25) 84 Button('Back') 85 .onClick(() => { 86 this.getUIContext().getRouter().back(); 87 }) 88 } 89 } 90} 91``` 92 93In the preceding example: 94 951. After the **changeBookName** button on page 1 is clicked, the **name** property of the **bookTest** variable is changed, triggering the **onMessageChange** method registered in @Monitor. 96 972. After the **go to next page** button on page 1 is displayed, the application navigates to page 2, and the **bookTest** state variable is updated after a 1s delay. When **bookTest** is updated, page 1 is already in the inactive state, where the [@Local](./arkts-new-local.md) decorated state variable **bookTest** does not respond to updates. Therefore, the @Monitor is not called, and no UI re-rendering occurs for nodes bound to this state variable. 98 99The trace information is shown below. 100 101 102 1033. After the **Back** button is clicked, page 2 is destroyed, and the state of page 1 changes from inactive to active. The update of the **bookTest** state variable is now observed. As a result, the **onMessageChange** method registered in @Monitor is called, and the bound **Text** component updates its display content. 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. The remaining **TabContent** components are created only when the corresponding tab is switched to. 112 113The figure below shows this mechanism. 114 115 116```ts 117@Entry 118@ComponentV2 119struct TabContentTest { 120 @Local message: number = 0; 121 @Local data: number[] = [0, 1]; 122 123 build() { 124 Row() { 125 Column() { 126 Button('change message').onClick(() => { 127 this.message++; 128 }) 129 130 Tabs() { 131 ForEach(this.data, (item: number) => { 132 TabContent() { 133 FreezeChild({ message: this.message, index: item }) 134 }.tabBar(`tab${item}`) 135 }, (item: number) => item.toString()) 136 } 137 } 138 .width('100%') 139 } 140 .height('100%') 141 } 142} 143 144@ComponentV2({ freezeWhenInactive: true }) 145struct FreezeChild { 146 @Param message: number = 0; 147 @Param index: number = 0; 148 149 @Monitor('message') onMessageUpdated(mon: IMonitor) { 150 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`); 151 } 152 153 build() { 154 Text("message" + `${this.message}, index: ${this.index}`) 155 .fontSize(50) 156 .fontWeight(FontWeight.Bold) 157 } 158} 159``` 160 161In the preceding example: 162 1631. When **change message** is clicked, the value of **message** changes, triggering the @Monitor decorated **onMessageUpdated** callback of the **TabContent** component being displayed. 164 1652. When **tab1** in **TabBar** is clicked to navigate to another **TabContent** component, the component switches from inactive to active, triggering the corresponding @Monitor decorated **onMessageUpdated** callback. 166 1673. When **change message** is clicked again, the value of **message** changes, triggering only the @Monitor decorated **onMessageUpdated** callback of the **TabContent** component being displayed. Other inactive **TabContent** components do not trigger @Monitor decorated callbacks. 168 169 170 171 172### Navigation 173 174- When a **NavDestination** component becomes invisible, its child custom components are set to the inactive state, where re-rendering is suspended. When this **NavDestination** component is visible again, its child custom components are restored to the active state and the @Monitor decorated callback is triggered for UI re-rendering. 175 176```ts 177@Entry 178@ComponentV2 179struct MyNavigationTestStack { 180 @Provider('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 181 @Local message: number = 0; 182 183 @Monitor('message') info() { 184 console.info(`freeze-test MyNavigation message callback ${this.message}`); 185 } 186 187 @Builder 188 PageMap(name: string) { 189 if (name === 'pageOne') { 190 PageOneStack({ message: this.message }) 191 } else if (name === 'pageTwo') { 192 PageTwoStack({ message: this.message }) 193 } else if (name === 'pageThree') { 194 PageThreeStack({ message: this.message }) 195 } 196 } 197 198 build() { 199 Column() { 200 Button('change message') 201 .onClick(() => { 202 this.message++; 203 }) 204 Navigation(this.pageInfo) { 205 Column() { 206 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 207 .onClick(() => { 208 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 209 }) 210 } 211 }.title('NavIndex') 212 .navDestination(this.PageMap) 213 .mode(NavigationMode.Stack) 214 } 215 } 216} 217 218@ComponentV2 219struct PageOneStack { 220 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 221 @Local index: number = 1; 222 @Param message: number = 0; 223 224 build() { 225 NavDestination() { 226 Column() { 227 NavigationContentMsgStack({ message: this.message, index: this.index }) 228 Text("cur stack size:" + `${this.pageInfo.size()}`) 229 .fontSize(30) 230 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 231 .onClick(() => { 232 this.pageInfo.pushPathByName('pageTwo', null); 233 }) 234 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 235 .onClick(() => { 236 this.pageInfo.pop(); 237 }) 238 }.width('100%').height('100%') 239 }.title('pageOne') 240 .onBackPressed(() => { 241 this.pageInfo.pop(); 242 return true; 243 }) 244 } 245} 246 247@ComponentV2 248struct PageTwoStack { 249 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 250 @Local index: number = 2; 251 @Param message: number = 0; 252 253 build() { 254 NavDestination() { 255 Column() { 256 NavigationContentMsgStack({ message: this.message, index: this.index }) 257 Text("cur stack size:" + `${this.pageInfo.size()}`) 258 .fontSize(30) 259 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 260 .onClick(() => { 261 this.pageInfo.pushPathByName('pageThree', null); 262 }) 263 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 264 .onClick(() => { 265 this.pageInfo.pop(); 266 }) 267 } 268 }.title('pageTwo') 269 .onBackPressed(() => { 270 this.pageInfo.pop(); 271 return true; 272 }) 273 } 274} 275 276@ComponentV2 277struct PageThreeStack { 278 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 279 @Local index: number = 3; 280 @Param message: number = 0; 281 282 build() { 283 NavDestination() { 284 Column() { 285 NavigationContentMsgStack({ message: this.message, index: this.index }) 286 Text("cur stack size:" + `${this.pageInfo.size()}`) 287 .fontSize(30) 288 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 289 .height(40) 290 .onClick(() => { 291 this.pageInfo.pushPathByName('pageOne', null); 292 }) 293 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 294 .height(40) 295 .onClick(() => { 296 this.pageInfo.pop(); 297 }) 298 } 299 }.title('pageThree') 300 .onBackPressed(() => { 301 this.pageInfo.pop(); 302 return true; 303 }) 304 } 305} 306 307@ComponentV2({ freezeWhenInactive: true }) 308struct NavigationContentMsgStack { 309 @Param message: number = 0; 310 @Param index: number = 0; 311 312 @Monitor('message') info() { 313 console.info(`freeze-test NavigationContent message callback ${this.message}`); 314 console.info(`freeze-test ---- called by content ${this.index}`); 315 } 316 317 build() { 318 Column() { 319 Text("msg:" + `${this.message}`) 320 .fontSize(30) 321 } 322 } 323} 324``` 325 326In the preceding example: 327 3281. When **change message** is clicked, the value of **message** changes, triggering the @Monitor decorated **info** method of the **MyNavigationTestStack** component being displayed. 329 3302. When **Next Page** is clicked, the page is switched to **PageOne** and the **PageOneStack** node is created. 331 3323. When **change message** is clicked again, the value of **message** changes, triggering only the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component in **PageOneStack**. 333 3344. 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. 335 3365. When **change message** is clicked again, the value of **message** changes, triggering only the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component in **PageTwoStack**. The child custom components in **NavDestination** that are not at the top of the navigation stack are in the inactive state, and their @Monitor decorated methods are not triggered. 337 3386. 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. 339 3407. When **change message** is clicked again, the value of **message** changes, triggering only the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component in **PageThreeStack**. The child custom components in **NavDestination** that are not at the top of the navigation stack are in the inactive state, and their @Monitor decorated methods are not triggered. 341 3428. After **Back Page** is clicked to return to **PageTwo**, the state of the **PageTwoStack** node changes from inactive to active, triggering the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component. 343 3449. After **Back Page** is clicked again to return to **PageOne**, the state of the **PageOneStack** node changes from inactive to active, triggering the @Monitor decorated **info** method of the **NavigationContentMsgStack** child component. 345 34610. When **Back Page** is clicked once more, the UI is switched to the initial page. 347 348 349 350### Repeat 351 352> **NOTE** 353> 354> Repeat supports custom component freezing since API version 18. 355 356Freezing custom components in the cache pool of **Repeat** prevents unnecessary component re-renders. Before proceeding, reviewing the [Node Update and Reuse Mechanism](./arkts-new-rendering-control-repeat.md#node-update-and-reuse-mechanism) is recommended. 357 358```ts 359@Entry 360@ComponentV2 361struct RepeatVirtualScrollFreeze { 362 @Local simpleList: Array<string> = []; 363 @Local bgColor: Color = Color.Pink; 364 365 aboutToAppear(): void { 366 for (let i = 0; i < 7; i++) { 367 this.simpleList.push(`item${i}`); 368 } 369 } 370 371 build() { 372 Column() { 373 Row() { 374 Button(`Reduce length to 5`) 375 .onClick(() => { 376 this.simpleList = this.simpleList.slice(0, 5); 377 }) 378 Button(`Change bgColor`) 379 .onClick(() => { 380 this.bgColor = this.bgColor == Color.Pink ? Color.Blue : Color.Pink; 381 }) 382 } 383 384 List() { 385 Repeat(this.simpleList) 386 .each((obj: RepeatItem<string>) => { 387 }) 388 .key((item: string, index: number) => item) 389 .virtualScroll({ totalCount: this.simpleList.length }) 390 .templateId(() => `a`) 391 .template(`a`, (ri) => { 392 ChildComponent({ 393 message: ri.item, 394 bgColor: this.bgColor 395 }) 396 }, { cachedCount: 2 }) 397 } 398 .cachedCount(0) 399 .height(500) 400 } 401 .height(`100%`) 402 } 403} 404 405// Enable component freezing. 406@ComponentV2({ freezeWhenInactive: true }) 407struct ChildComponent { 408 @Param @Require message: string = ``; 409 @Param @Require bgColor: Color = Color.Pink; 410 @Monitor(`bgColor`) 411 onBgColorChange(monitor: IMonitor) { 412 // When the value of bgColor changes, the components in the cache pool are not re-rendered, so no log is printed. 413 console.info(`repeat---bgColor change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 414 } 415 416 build() { 417 Text(`[a]: ${this.message}`) 418 .fontSize(50) 419 .backgroundColor(this.bgColor) 420 } 421} 422``` 423 424In the preceding example: 425 426After **Reduce length to 5** is clicked, the two removed components enter the cache pool of **Repeat**. Then, clicking **Change bgColor** changes the value of **bgColor**, triggering component re-rendering. 427 428With component freezing enabled (**freezeWhenInactive: true**), only the @Monitor decorated **onBgColorChange** callback in the remaining active nodes is triggered. In the example, the five active nodes are re-rendered, causing five logs to be printed. 429 430 431 432```ts 433// Disable component freezing. 434@ComponentV2({ freezeWhenInactive: false }) 435struct ChildComponent { 436 @Param @Require message: string = ``; 437 @Param @Require bgColor: Color = Color.Pink; 438 @Monitor(`bgColor`) 439 onBgColorChange(monitor: IMonitor) { 440 // When the value of bgColor changes, components in the cache pool are also re-rendered, printing logs. 441 console.info(`repeat---bgColor change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 442 } 443 444 build() { 445 Text(`[a]: ${this.message}`) 446 .fontSize(50) 447 .backgroundColor(this.bgColor) 448 } 449} 450``` 451 452When component freezing is disabled (**freezeWhenInactive: false** - the default setting when **freezeWhenInactive** is not specified), the @Monitor decorated **onBgColorChange** callback is triggered for both the remaining active components and components in the cache pool. This means all seven components are re-rendered, printing seven logs. 453 454 455 456### Component Freezing for Child Components Only 457 458You can selectively freeze specific child components by setting **freezeWhenInactive: true** only on those child components. 459 460```ts 461// Page1.ets 462@ObservedV2 463class Book { 464 @Trace name: string = 'TS'; 465 466 constructor(name: string) { 467 this.name = name; 468 } 469} 470 471@Entry 472@ComponentV2 473struct Page1 { 474 pageInfo: NavPathStack = new NavPathStack(); 475 476 build() { 477 Column() { 478 Navigation(this.pageInfo) { 479 Child() 480 481 Button('Go to next page').fontSize(30) 482 .onClick(() => { 483 this.pageInfo.pushPathByName('Page2', null); 484 }) 485 } 486 } 487 } 488} 489 490@ComponentV2({ freezeWhenInactive: true }) 491export struct Child { 492 @Local bookTest: Book = new Book(`A Midsummer Night's Dream`); 493 494 @Monitor('bookTest.name') 495 onMessageChange(monitor: IMonitor) { 496 console.info(`The book name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 497 } 498 499 textUpdate(): number { 500 console.info('The text is update'); 501 return 25; 502 } 503 504 build() { 505 Column() { 506 Text(`The book name is ${this.bookTest.name}`).fontSize(this.textUpdate()) 507 508 Button('change BookName') 509 .onClick(() => { 510 setTimeout(() => { 511 this.bookTest = new Book("Jane Austen's Pride and Prejudice"); 512 }, 3000); 513 }) 514 } 515 } 516} 517``` 518 519```ts 520// Page2.ets 521@Builder 522function Page2Builder() { 523 Page2() 524} 525 526@ComponentV2 527struct Page2 { 528 pathStack: NavPathStack = new NavPathStack(); 529 530 build() { 531 NavDestination() { 532 Column() { 533 Text('This is the Page2') 534 535 Button('Back').fontSize(30) 536 .onClick(() => { 537 this.pathStack.pop(); 538 }) 539 } 540 }.onReady((context: NavDestinationContext) => { 541 this.pathStack = context.pathStack; 542 }) 543 } 544} 545``` 546 547When using **Navigation**, create a **route_map.json** file as shown below in the **src/main/resources/base/profile** directory, replacing the value of **pageSourceFile** with the actual path to **Page2**. Then, add **"routerMap": "$profile: route_map"** to the **module.json5** file. 548 549```json 550{ 551 "routerMap": [ 552 { 553 "name": "Page2", 554 "pageSourceFile": "src/main/ets/pages/Page2.ets", 555 "buildFunction": "Page2Builder", 556 "data": { 557 "description" : "This is the Page2" 558 } 559 } 560 ] 561} 562``` 563 564In the preceding example: 565- The child component **Child** in **Page1** has **freezeWhenInactive: true** configured. 566- During the test, click the **change BookName** button; within 3 seconds, click the **Go to next page** button. When **bookTest** is updated, **Page1** is already in the inactive state after navigation to **Page2**. Due to component freezing enabled for **Child**, the **@Local bookTest** state variable does not respond to updates. This means that the @Monitor decorated callback will not be triggered, and any components associated with the state variable will not be re-rendered. 567- After the **Back** button is clicked to return to the previous page, the @Monitor decorated callback is triggered, and components associated with the state variable will be re-rendered. 568 569### Component Freezing Across Mixed Scenarios 570 571When component freezing is applied across different scenarios, freezing behavior varies by API version. Key differences exist when parent components have freezing enabled: 572 573- API version 17 or earlier: Thawing a parent component automatically thaws all its child components. 574 575- API version 18 or later: Thawing a parent component only thaws on-screen child components. For details, see [Mixing the Use of Components](./arkts-custom-components-freeze.md#mixing-the-use-of-components). 576 577**Mixed Use of Navigation and TabContent** 578 579```ts 580@ComponentV2 581struct ChildOfParamComponent { 582 @Require @Param child_val: number; 583 584 @Monitor('child_val') onChange(m: IMonitor) { 585 console.info(`Appmonitor ChildOfParamComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 586 } 587 588 build() { 589 Column() { 590 Text(`Child Param: ${this.child_val}`); 591 } 592 } 593} 594 595@ComponentV2 596struct ParamComponent { 597 @Require @Param val: number; 598 599 @Monitor('val') onChange(m: IMonitor) { 600 console.info(`Appmonitor ParamComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 601 } 602 603 build() { 604 Column() { 605 Text(`val: ${this.val}`); 606 ChildOfParamComponent({child_val: this.val}); 607 } 608 } 609} 610 611@ComponentV2 612struct DelayComponent { 613 @Require @Param delayVal1: number; 614 615 @Monitor('delayVal1') onChange(m: IMonitor) { 616 console.info(`Appmonitor DelayComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 617 } 618 619 build() { 620 Column() { 621 Text(`Delay Param: ${this.delayVal1}`); 622 } 623 } 624} 625 626@ComponentV2 ({freezeWhenInactive: true}) 627struct TabsComponent { 628 private controller: TabsController = new TabsController(); 629 @Local tabState: number = 47; 630 631 @Monitor('tabState') onChange(m: IMonitor) { 632 console.info(`Appmonitor TabsComponent: changed ${m.dirty[0]}: ${m.value()?.before} -> ${m.value()?.now}`); 633 } 634 635 build() { 636 Column({space: 10}) { 637 Button(`Incr state ${this.tabState}`) 638 .fontSize(25) 639 .onClick(() => { 640 console.info('Button increment state value'); 641 this.tabState = this.tabState + 1; 642 }) 643 644 Tabs({ barPosition: BarPosition.Start, index: 0, controller: this.controller}) { 645 TabContent() { 646 ParamComponent({val: this.tabState}); 647 }.tabBar('Update') 648 TabContent() { 649 DelayComponent({delayVal1: this.tabState}); 650 }.tabBar('DelayUpdate') 651 } 652 .vertical(false) 653 .scrollable(true) 654 .barMode(BarMode.Fixed) 655 .barWidth(400).barHeight(150).animationDuration(400) 656 .width('100%') 657 .height(200) 658 .backgroundColor(0xF5F5F5) 659 } 660 } 661} 662 663@Entry 664@Component 665struct MyNavigationTestStack { 666 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 667 668 @Builder 669 PageMap(name: string) { 670 if (name === 'pageOne') { 671 PageOneStack() 672 } else if (name === 'pageTwo') { 673 PageTwoStack() 674 } 675 } 676 677 build() { 678 Column() { 679 Navigation(this.pageInfo) { 680 Column() { 681 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 682 .width('80%') 683 .height(40) 684 .margin(20) 685 .onClick(() => { 686 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 687 }) 688 } 689 }.title('NavIndex') 690 .navDestination(this.PageMap) 691 .mode(NavigationMode.Stack) 692 } 693 } 694} 695 696@Component 697struct PageOneStack { 698 @Consume('pageInfo') pageInfo: NavPathStack; 699 700 build() { 701 NavDestination() { 702 Column() { 703 TabsComponent(); 704 705 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 706 .width('80%') 707 .height(40) 708 .margin(20) 709 .onClick(() => { 710 this.pageInfo.pushPathByName('pageTwo', null); 711 }) 712 }.width('100%').height('100%') 713 }.title('pageOne') 714 .onBackPressed(() => { 715 this.pageInfo.pop(); 716 return true; 717 }) 718 } 719} 720 721@Component 722struct PageTwoStack { 723 @Consume('pageInfo') pageInfo: NavPathStack; 724 725 build() { 726 NavDestination() { 727 Column() { 728 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 729 .width('80%') 730 .height(40) 731 .margin(20) 732 .onClick(() => { 733 this.pageInfo.pop(); 734 }) 735 }.width('100%').height('100%') 736 }.title('pageTwo') 737 .onBackPressed(() => { 738 this.pageInfo.pop(); 739 return true; 740 }) 741 } 742} 743``` 744 745For API version 17 or earlier: 746 747Navigating to the next page using the **Next page** button and then returning to the previous page will thaw all **TabContent** components. 748 749For API version 18 or later: 750 751Navigating to the next page using the **Next page** button and then returning to the previous page will thaw only the **TabContent** component being displayed. 752 753## Constraints 754 755The **FreezeBuildNode** example below demonstrates the constraint for using a [BuilderNode](../../reference/apis-arkui/js-apis-arkui-builderNode.md) with component freezing. When a BuilderNode is used within a frozen component hierarchy, its imperative mounting mechanism conflicts with the functionality of component freezing, which relies on parent-child relationships. As a result, the child components of the BuilderNode remain active, regardless of their parent's frozen state. 756 757``` 758import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 759 760// Define a Params class to pass parameters. 761@ObservedV2 762class Params { 763 // Singleton pattern to ensure that there is only one Params instance. 764 static singleton_: Params; 765 766 // Method for obtaining the Params instance. 767 static instance() { 768 if (!Params.singleton_) { 769 Params.singleton_ = new Params(0); 770 } 771 return Params.singleton_; 772 } 773 774 // Decorate the message attribute with the @Trace decorator so that its changes are observable. 775 @Trace message: string = "Hello"; 776 index: number = 0; 777 778 constructor(index: number) { 779 this.index = index; 780 } 781} 782 783// Define a buildNodeChild component that contains a message attribute and an index attribute. 784@ComponentV2 785struct buildNodeChild { 786 // Use the Params instance as the storage attribute. 787 storage: Params = Params.instance(); 788 @Param index: number = 0; 789 790 // Use the @Monitor decorator to listen for the changes of storage.message. 791 @Monitor("storage.message") 792 onMessageChange(monitor: IMonitor) { 793 console.info(`FreezeBuildNode buildNodeChild message callback func ${this.storage.message}, index:${this.index}`); 794 } 795 796 build() { 797 Text(`buildNode Child message: ${this.storage.message}`).fontSize(30) 798 } 799} 800 801// Define a buildText function that receives a Params parameter and constructs a Column component. 802@Builder 803function buildText(params: Params) { 804 Column() { 805 buildNodeChild({ index: params.index }) 806 } 807} 808 809class TextNodeController extends NodeController { 810 private textNode: BuilderNode<[Params]> | null = null; 811 private index: number = 0; 812 813 // The constructor receives an index parameter. 814 constructor(index: number) { 815 super(); 816 this.index = index; 817 } 818 819 // Create and return a FrameNode. 820 makeNode(context: UIContext): FrameNode | null { 821 this.textNode = new BuilderNode(context); 822 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.index)); 823 return this.textNode.getFrameNode(); 824 } 825} 826 827// Define an Index component that contains a message attribute and a data array. 828@Entry 829@ComponentV2 830struct Index { 831 // Use the Params instance as the storage attribute. 832 storage: Params = Params.instance(); 833 private data: number[] = [0, 1]; 834 835 build() { 836 Row() { 837 Column() { 838 Button("change").fontSize(30) 839 .onClick(() => { 840 this.storage.message += 'a'; 841 }) 842 843 Tabs() { 844 // Use Repeat to repeatedly render the TabContent component. 845 Repeat<number>(this.data) 846 .each((obj: RepeatItem<number>) => { 847 TabContent() { 848 FreezeBuildNode({ index: obj.item }) 849 .margin({ top: 20 }) 850 }.tabBar(`tab${obj.item}`) 851 }) 852 .key((item: number) => item.toString()) 853 } 854 } 855 } 856 .width('100%') 857 .height('100%') 858 } 859} 860 861// Define a FreezeBuildNode component that contains a message attribute and an index attribute. 862@ComponentV2({ freezeWhenInactive: true }) 863struct FreezeBuildNode { 864 // Use the Params instance as the storage attribute. 865 storage: Params = Params.instance(); 866 @Param index: number = 0; 867 868 // Use the @Monitor decorator to listen for the changes of storage.message. 869 @Monitor("storage.message") 870 onMessageChange(monitor: IMonitor) { 871 console.info(`FreezeBuildNode message callback func ${this.storage.message}, index: ${this.index}`); 872 } 873 874 build() { 875 NodeContainer(new TextNodeController(this.index)) 876 .width('100%') 877 .height('100%') 878 .backgroundColor('#FFF0F0F0') 879 } 880} 881``` 882 883After the **change** button is clicked, the value of **message** changes, resulting in both expected and unexpected behaviors: 884 885- Expected: The [@Watch](./arkts-watch.md) decorated **onMessageUpdated** callback of the **TabContent** component that is being displayed is triggered. 886- Unexpected: For **TabContent** components that are not being displayed, the @Watch decorated **onMessageUpdated** callbacks of child components under the BuilderNode are also triggered, indicating that these components are not frozen. 887 888 889