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> @ComponentV2 decorated custom component freezing is supported since API version 12. 10> 11> Different from freezing the @Component decorated components, custom components decorated by @ComponentV2 do not support freezing the cached list items in the **LazyForEach** scenario. 12 13 14## Use Scenarios 15 16### Page Routing 17 18> **NOTE** 19> 20> 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). 21 22- 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. 23For details, see the following. 24 25 26 27Page 1 28 29```ts 30import { router } from '@kit.ArkUI'; 31 32@ObservedV2 33export class Book { 34 @Trace name: string = "100"; 35 36 constructor(page: string) { 37 this.name = page; 38 } 39} 40 41@Entry 42@ComponentV2({ freezeWhenInactive: true }) 43export struct Page1 { 44 @Local bookTest: Book = new Book("A Midsummer Night's Dream"); 45 46 @Monitor("bookTest.name") 47 onMessageChange(monitor: IMonitor) { 48 console.log(`The book name change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 49 } 50 51 build() { 52 Column() { 53 Text(`Book name is ${this.bookTest.name}`).fontSize(25) 54 Button('changeBookName').fontSize(25) 55 .onClick(() => { 56 this.bookTest.name = "The Old Man and the Sea"; 57 }) 58 Button('go to next page').fontSize(25) 59 .onClick(() => { 60 router.pushUrl({ url: 'pages/Page2' }); 61 setTimeout(() => { 62 this.bookTest = new Book("Jane Austen oPride and Prejudice"); 63 }, 1000) 64 }) 65 } 66 } 67} 68``` 69 70Page 2 71 72```ts 73import { router } from '@kit.ArkUI'; 74 75@Entry 76@ComponentV2 77struct Page2 { 78 build() { 79 Column() { 80 Text(`This is the page2`).fontSize(25) 81 Button('Back') 82 .onClick(() => { 83 router.back(); 84 }) 85 } 86 } 87} 88``` 89 90In the preceding example: 91 921. 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. 93 942. 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. 95The trace diagram is as follows. 96 97 98 99 1003. 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. 101 102 103 104 105### TabContent 106 107- You can freeze invisible **TabContent** components in the **Tabs** container so that they do not trigger UI re-rendering. 108 109- 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. 110 111For details, see the following. 112 113 114 115```ts 116@Entry 117@ComponentV2 118struct TabContentTest { 119 @Local message: number = 0; 120 @Local data: number[] = [0, 1]; 121 122 build() { 123 Row() { 124 Column() { 125 Button('change message').onClick(() => { 126 this.message++; 127 }) 128 129 Tabs() { 130 ForEach(this.data, (item: number) => { 131 TabContent() { 132 FreezeChild({ message: this.message, index: item }) 133 }.tabBar(`tab${item}`) 134 }, (item: number) => item.toString()) 135 } 136 } 137 .width('100%') 138 } 139 .height('100%') 140 } 141} 142 143@ComponentV2({ freezeWhenInactive: true }) 144struct FreezeChild { 145 @Param message: number = 0; 146 @Param index: number = 0; 147 148 @Monitor('message') onMessageUpdated(mon: IMonitor) { 149 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`); 150 } 151 152 build() { 153 Text("message" + `${this.message}, index: ${this.index}`) 154 .fontSize(50) 155 .fontWeight(FontWeight.Bold) 156 } 157} 158``` 159 160In the preceding example: 161 1621. When **change message** is clicked, the value of **message** changes, and the @Monitor decorated **onMessageUpdated** method of the **TabContent** component being displayed is called. 163 1642. 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. 165 1663. 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. 167 168 169 170 171### Navigation 172 173- You can freeze an invisible page so that it does not trigger UI re-rendering. When the user returns to this page, a re-render is triggered through an @Monitor decorated callback. 174 175```ts 176@Entry 177@ComponentV2 178struct MyNavigationTestStack { 179 @Provider('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 180 @Local message: number = 0; 181 182 @Monitor('message') info() { 183 console.info(`freeze-test MyNavigation message callback ${this.message}`); 184 } 185 186 @Builder 187 PageMap(name: string) { 188 if (name === 'pageOne') { 189 pageOneStack({ message: this.message }) 190 } else if (name === 'pageTwo') { 191 pageTwoStack({ message: this.message }) 192 } else if (name === 'pageThree') { 193 pageThreeStack({ message: this.message }) 194 } 195 } 196 197 build() { 198 Column() { 199 Button('change message') 200 .onClick(() => { 201 this.message++; 202 }) 203 Navigation(this.pageInfo) { 204 Column() { 205 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 206 .onClick(() => { 207 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 208 }) 209 } 210 }.title('NavIndex') 211 .navDestination(this.PageMap) 212 .mode(NavigationMode.Stack) 213 } 214 } 215} 216 217@ComponentV2 218struct pageOneStack { 219 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 220 @Local index: number = 1; 221 @Param message: number = 0; 222 223 build() { 224 NavDestination() { 225 Column() { 226 NavigationContentMsgStack({ message: this.message, index: this.index }) 227 Text("cur stack size:" + `${this.pageInfo.size()}`) 228 .fontSize(30) 229 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 230 .onClick(() => { 231 this.pageInfo.pushPathByName('pageTwo', null); 232 }) 233 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 234 .onClick(() => { 235 this.pageInfo.pop(); 236 }) 237 }.width('100%').height('100%') 238 }.title('pageOne') 239 .onBackPressed(() => { 240 this.pageInfo.pop(); 241 return true; 242 }) 243 } 244} 245 246@ComponentV2 247struct pageTwoStack { 248 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 249 @Local index: number = 2; 250 @Param message: number = 0; 251 252 build() { 253 NavDestination() { 254 Column() { 255 NavigationContentMsgStack({ message: this.message, index: this.index }) 256 Text("cur stack size:" + `${this.pageInfo.size()}`) 257 .fontSize(30) 258 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 259 .onClick(() => { 260 this.pageInfo.pushPathByName('pageThree', null); 261 }) 262 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 263 .onClick(() => { 264 this.pageInfo.pop(); 265 }) 266 } 267 }.title('pageTwo') 268 .onBackPressed(() => { 269 this.pageInfo.pop(); 270 return true; 271 }) 272 } 273} 274 275@ComponentV2 276struct pageThreeStack { 277 @Consumer('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 278 @Local index: number = 3; 279 @Param message: number = 0; 280 281 build() { 282 NavDestination() { 283 Column() { 284 NavigationContentMsgStack({ message: this.message, index: this.index }) 285 Text("cur stack size:" + `${this.pageInfo.size()}`) 286 .fontSize(30) 287 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 288 .height(40) 289 .onClick(() => { 290 this.pageInfo.pushPathByName('pageOne', null); 291 }) 292 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 293 .height(40) 294 .onClick(() => { 295 this.pageInfo.pop(); 296 }) 297 } 298 }.title('pageThree') 299 .onBackPressed(() => { 300 this.pageInfo.pop(); 301 return true; 302 }) 303 } 304} 305 306@ComponentV2({ freezeWhenInactive: true }) 307struct NavigationContentMsgStack { 308 @Param message: number = 0; 309 @Param index: number = 0; 310 311 @Monitor('message') info() { 312 console.info(`freeze-test NavigationContent message callback ${this.message}`); 313 console.info(`freeze-test ---- called by content ${this.index}`); 314 } 315 316 build() { 317 Column() { 318 Text("msg:" + `${this.message}`) 319 .fontSize(30) 320 } 321 } 322} 323``` 324 325In the preceding example: 326 3271. When **change message** is clicked, the value of **message** changes, and the @Monitor decorated **info** method of the **MyNavigationTestStack** component being displayed is called. 328 3292. When **Next Page** is clicked, the page is switched to **PageOne** and the **pageOneStack** node is created. 330 3313. 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. 332 3334. 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. 334 3355. 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. 336 3376. 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. 338 3397. 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. 340 3418. 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. 342 3439. 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. 344 34510. When **Back Page** is clicked, the page is switched to the initial page. 346 347 348 349## Constraints 350As 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. 351 352``` 353import { BuilderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 354 355// Define a Params class to pass parameters. 356@ObservedV2 357class Params { 358 // Singleton mode. Ensure that there is only one Params instance. 359 static singleton_: Params; 360 361 // Method for obtaining the Params instance. 362 static instance() { 363 if (!Params.singleton_) { 364 Params.singleton_ = new Params(0); 365 } 366 return Params.singleton_; 367 } 368 369 // Use the @Trace decorator to decorate the message attribute so that its changes are observable. 370 @Trace message: string = "Hello"; 371 index: number = 0; 372 373 constructor(index: number) { 374 this.index = index; 375 } 376} 377 378// Define a buildNodeChild component that contains a message attribute and an index attribute. 379@ComponentV2 380struct buildNodeChild { 381 // Use the Params instance as the storage attribute. 382 storage: Params = Params.instance(); 383 @Param index: number = 0; 384 385 // Use the @Monitor decorator to listen for the changes of storage.message. 386 @Monitor("storage.message") 387 onMessageChange(monitor: IMonitor) { 388 console.log(`FreezeBuildNode buildNodeChild message callback func ${this.storage.message}, index:${this.index}`); 389 } 390 391 build() { 392 Text(`buildNode Child message: ${this.storage.message}`).fontSize(30) 393 } 394} 395 396// Define a buildText function that receives a Params parameter and constructs a Column component. 397@Builder 398function buildText(params: Params) { 399 Column() { 400 buildNodeChild({ index: params.index }) 401 } 402} 403 404class TextNodeController extends NodeController { 405 private textNode: BuilderNode<[Params]> | null = null; 406 private index: number = 0; 407 408 // The constructor receives an index parameter. 409 constructor(index: number) { 410 super(); 411 this.index = index; 412 } 413 414 // Create and return a FrameNode. 415 makeNode(context: UIContext): FrameNode | null { 416 this.textNode = new BuilderNode(context); 417 this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.index)); 418 return this.textNode.getFrameNode(); 419 } 420} 421 422// Define an index component that contains a message attribute and a data array. 423@Entry 424@ComponentV2 425struct Index { 426 // Use the Params instance as the storage attribute. 427 storage: Params = Params.instance(); 428 private data: number[] = [0, 1]; 429 430 build() { 431 Row() { 432 Column() { 433 Button("change").fontSize(30) 434 .onClick(() => { 435 this.storage.message += 'a'; 436 }) 437 438 Tabs() { 439 // Use Repeat to repeatedly render the TabContent component. 440 Repeat<number>(this.data) 441 .each((obj: RepeatItem<number>) => { 442 TabContent() { 443 FreezeBuildNode({ index: obj.item }) 444 .margin({ top: 20 }) 445 }.tabBar(`tab${obj.item}`) 446 }) 447 .key((item: number) => item.toString()) 448 } 449 } 450 } 451 .width('100%') 452 .height('100%') 453 } 454} 455 456// Define a FreezeBuildNode component that contains a message attribute and an index attribute. 457@ComponentV2({ freezeWhenInactive: true }) 458struct FreezeBuildNode { 459 // Use the Params instance as the storage attribute. 460 storage: Params = Params.instance(); 461 @Param index: number = 0; 462 463 // Use the @Monitor decorator to listen for the changes of storage.message. 464 @Monitor("storage.message") 465 onMessageChange(monitor: IMonitor) { 466 console.log(`FreezeBuildNode message callback func ${this.storage.message}, index: ${this.index}`); 467 } 468 469 build() { 470 NodeContainer(new TextNodeController(this.index)) 471 .width('100%') 472 .height('100%') 473 .backgroundColor('#FFF0F0F0') 474 } 475} 476``` 477 478Click **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. 479 480 481