• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![freezeInPage](./figures/freezeInPage.png)
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![Example Image](./figures/freeze1.png)
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![freezeV2Page](./figures/freezeV2page.gif)
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![freezeWithTab](./figures/freezewithTabs.png)
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![TabContent.gif](figures/TabContent.gif)
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![navigation-freeze.gif](figures/navigation-freeze.gif)
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![freeze_repeat_L2.gif](figures/freeze_repeat_L2.gif)
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![freeze_repeat_L2_unfreeze.gif](figures/freeze_repeat_L2_unfreeze.gif)
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![builderNode.gif](figures/builderNode.gif)
889