1# Freezing a Custom Component 2 3When a custom component is inactive, it can be frozen so that its state variable does not respond to updates. That is, the @Watch 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>**, **LazyForEach**, and **\<Navigation>**. 4 5 6> **NOTE** 7> 8> Custom component freezing is supported since API version 11. 9 10## Use Scenarios 11 12### Page Routing 13 14- When page A calls the **router.pushUrl** API to jump to page B, page A is hidden and invisible. In this case, if the state variable on page A is updated, page A is not re-rendered. 15 16- The freezing feature does not work when the application is running in the background. 17 18Page A: 19 20```ts 21import { router } from '@kit.ArkUI'; 22 23@Entry 24@Component({ freezeWhenInactive: true }) 25struct FirstTest { 26 @StorageLink('PropA') @Watch("first") storageLink: number = 47; 27 28 first() { 29 console.info("first page " + `${this.storageLink}`) 30 } 31 32 build() { 33 Column() { 34 Text(`From fist Page ${this.storageLink}`).fontSize(50) 35 Button('first page storageLink + 1').fontSize(30) 36 .onClick(() => { 37 this.storageLink += 1 38 }) 39 Button('go to next page').fontSize(30) 40 .onClick(() => { 41 router.pushUrl({ url: 'pages/second' }) 42 }) 43 } 44 } 45} 46``` 47 48Page B: 49 50```ts 51import { router } from '@kit.ArkUI'; 52 53@Entry 54@Component({ freezeWhenInactive: true }) 55struct SecondTest { 56 @StorageLink('PropA') @Watch("second") storageLink2: number = 1; 57 58 second() { 59 console.info("second page: " + `${this.storageLink2}`) 60 } 61 62 build() { 63 Column() { 64 65 Text(`second Page ${this.storageLink2}`).fontSize(50) 66 Button('Change Divider.strokeWidth') 67 .onClick(() => { 68 router.back() 69 }) 70 71 Button('second page storageLink2 + 2').fontSize(30) 72 .onClick(() => { 73 this.storageLink2 += 2 74 }) 75 76 } 77 } 78} 79``` 80 81In the preceding example: 82 831. When the button **first page storageLink + 1** on page A is clicked, the **storageLink** state variable is updated, and the @Watch decorated **first** method is called. 84 852. Through **router.pushUrl({url:'pages/second'})**, page B is displayed, and page A is hidden with its state changing from active to inactive. 86 873. When the button **this.storageLink2 += 2** on page B is clicked, only the @Watch decorated **second** method of page B is called, because page A has been frozen when inactive. 88 894. When the **back** button is clicked, page B is destroyed, and page A changes from inactive to active. At this time, if the state variable of page A is updated, the @Watch decorated **first** method of page A is called again. 90 91 92### TabContent 93 94- You can freeze invisible **TabContent** components in the **Tabs** container so that they do not trigger UI re-rendering. 95 96- 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. 97 98```ts 99@Entry 100@Component 101struct TabContentTest { 102 @State @Watch("onMessageUpdated") message: number = 0; 103 private data: number[] = [0, 1] 104 105 onMessageUpdated() { 106 console.info(`TabContent message callback func ${this.message}`) 107 } 108 109 build() { 110 Row() { 111 Column() { 112 Button('change message').onClick(() => { 113 this.message++ 114 }) 115 116 Tabs() { 117 ForEach(this.data, (item: number) => { 118 TabContent() { 119 FreezeChild({ message: this.message, index: item }) 120 }.tabBar(`tab${item}`) 121 }, (item: number) => item.toString()) 122 } 123 } 124 .width('100%') 125 } 126 .height('100%') 127 } 128} 129 130@Component({ freezeWhenInactive: true }) 131struct FreezeChild { 132 @Link @Watch("onMessageUpdated") message: number 133 private index: number = 0 134 135 onMessageUpdated() { 136 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) 137 } 138 139 build() { 140 Text("message" + `${this.message}, index: ${this.index}`) 141 .fontSize(50) 142 .fontWeight(FontWeight.Bold) 143 } 144} 145``` 146 147In the preceding example: 148 1491. When **change message** is clicked, the value of **message** changes, and the @Watch decorated **onMessageUpdated** method of the **TabContent** component being displayed is called. 150 1512. When you click **two** to switch to another **TabContent** component, it switches from inactive to active, and the corresponding @Watch decorated **onMessageUpdated** method is called. 152 1533. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **onMessageUpdated** method of the **TabContent** component being displayed is called. 154 155 156 157 158### LazyForEach 159 160- You can freeze custom components cached in **LazyForEach** so that they do not trigger UI re-rendering. 161 162```ts 163// Basic implementation of IDataSource to handle data listener 164class BasicDataSource implements IDataSource { 165 private listeners: DataChangeListener[] = []; 166 private originDataArray: string[] = []; 167 168 public totalCount(): number { 169 return 0; 170 } 171 172 public getData(index: number): string { 173 return this.originDataArray[index]; 174 } 175 176 // This method is called by the framework to add a listener to the LazyForEach data source. 177 registerDataChangeListener(listener: DataChangeListener): void { 178 if (this.listeners.indexOf(listener) < 0) { 179 console.info('add listener'); 180 this.listeners.push(listener); 181 } 182 } 183 184 // This method is called by the framework to remove the listener from the LazyForEach data source. 185 unregisterDataChangeListener(listener: DataChangeListener): void { 186 const pos = this.listeners.indexOf(listener); 187 if (pos >= 0) { 188 console.info('remove listener'); 189 this.listeners.splice(pos, 1); 190 } 191 } 192 193 // Notify LazyForEach that all child components need to be reloaded. 194 notifyDataReload(): void { 195 this.listeners.forEach(listener => { 196 listener.onDataReloaded(); 197 }) 198 } 199 200 // Notify LazyForEach that a child component needs to be added for the data item with the specified index. 201 notifyDataAdd(index: number): void { 202 this.listeners.forEach(listener => { 203 listener.onDataAdd(index); 204 }) 205 } 206 207 // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt. 208 notifyDataChange(index: number): void { 209 this.listeners.forEach(listener => { 210 listener.onDataChange(index); 211 }) 212 } 213 214 // Notify LazyForEach that the child component that matches the specified index needs to be deleted. 215 notifyDataDelete(index: number): void { 216 this.listeners.forEach(listener => { 217 listener.onDataDelete(index); 218 }) 219 } 220} 221 222class MyDataSource extends BasicDataSource { 223 private dataArray: string[] = []; 224 225 public totalCount(): number { 226 return this.dataArray.length; 227 } 228 229 public getData(index: number): string { 230 return this.dataArray[index]; 231 } 232 233 public addData(index: number, data: string): void { 234 this.dataArray.splice(index, 0, data); 235 this.notifyDataAdd(index); 236 } 237 238 public pushData(data: string): void { 239 this.dataArray.push(data); 240 this.notifyDataAdd(this.dataArray.length - 1); 241 } 242} 243 244@Entry 245@Component 246struct LforEachTest { 247 private data: MyDataSource = new MyDataSource(); 248 @State @Watch("onMessageUpdated") message: number = 0; 249 250 onMessageUpdated() { 251 console.info(`LazyforEach message callback func ${this.message}`) 252 } 253 254 aboutToAppear() { 255 for (let i = 0; i <= 20; i++) { 256 this.data.pushData(`Hello ${i}`) 257 } 258 } 259 260 build() { 261 Column() { 262 Button('change message').onClick(() => { 263 this.message++ 264 }) 265 List({ space: 3 }) { 266 LazyForEach(this.data, (item: string) => { 267 ListItem() { 268 FreezeChild({ message: this.message, index: item }) 269 } 270 }, (item: string) => item) 271 }.cachedCount(5).height(500) 272 } 273 274 } 275} 276 277@Component({ freezeWhenInactive: true }) 278struct FreezeChild { 279 @Link @Watch("onMessageUpdated") message: number; 280 private index: string = ""; 281 282 aboutToAppear() { 283 console.info(`FreezeChild aboutToAppear index: ${this.index}`) 284 } 285 286 onMessageUpdated() { 287 console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) 288 } 289 290 build() { 291 Text("message" + `${this.message}, index: ${this.index}`) 292 .width('90%') 293 .height(160) 294 .backgroundColor(0xAFEEEE) 295 .textAlign(TextAlign.Center) 296 .fontSize(30) 297 .fontWeight(FontWeight.Bold) 298 } 299} 300``` 301 302In the preceding example: 303 3041. When **change message** is clicked, the value of **message** changes, the @Watch decorated **onMessageUpdated** method of the list items being displayed is called, and that of the cached list items is not called. (If the component is not frozen, the @Watch decorated **onMessageUpdated** method of both list items that are being displayed and cached list items is called.) 305 3062. When a list item moves from outside the list content area into the list content area, it switches from inactive to active, and the corresponding @Watch decorated **onMessageUpdated** method is called. 307 3083. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **onMessageUpdated** method of the list items being displayed is called. 309 310 311 312### Navigation 313 314- 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 @Watch callback is triggered to re-render the page. 315 316- In the following example, **NavigationContentMsgStack** is set to the inactive state, which does not respond to the change of the state variables, and does not trigger component re-rendering. 317 318```ts 319@Entry 320@Component 321struct MyNavigationTestStack { 322 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 323 @State @Watch("info") message: number = 0; 324 @State logNumber: number = 0; 325 326 info() { 327 console.info(`freeze-test MyNavigation message callback ${this.message}`); 328 } 329 330 @Builder 331 PageMap(name: string) { 332 if (name === 'pageOne') { 333 pageOneStack({ message: this.message, logNumber: this.logNumber }) 334 } else if (name === 'pageTwo') { 335 pageTwoStack({ message: this.message, logNumber: this.logNumber }) 336 } else if (name === 'pageThree') { 337 pageThreeStack({ message: this.message, logNumber: this.logNumber }) 338 } 339 } 340 341 build() { 342 Column() { 343 Button('change message') 344 .onClick(() => { 345 this.message++; 346 }) 347 Navigation(this.pageInfo) { 348 Column() { 349 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 350 .width('80%') 351 .height(40) 352 .margin(20) 353 .onClick(() => { 354 this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack. 355 }) 356 } 357 }.title('NavIndex') 358 .navDestination(this.PageMap) 359 .mode(NavigationMode.Stack) 360 } 361 } 362} 363 364@Component 365struct pageOneStack { 366 @Consume('pageInfo') pageInfo: NavPathStack; 367 @State index: number = 1; 368 @Link message: number; 369 @Link logNumber: number; 370 371 build() { 372 NavDestination() { 373 Column() { 374 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 375 Text("cur stack size:" + `${this.pageInfo.size()}`) 376 .fontSize(30) 377 .fontWeight(FontWeight.Bold) 378 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 379 .width('80%') 380 .height(40) 381 .margin(20) 382 .onClick(() => { 383 this.pageInfo.pushPathByName('pageTwo', null); 384 }) 385 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 386 .width('80%') 387 .height(40) 388 .margin(20) 389 .onClick(() => { 390 this.pageInfo.pop(); 391 }) 392 }.width('100%').height('100%') 393 }.title('pageOne') 394 .onBackPressed(() => { 395 this.pageInfo.pop(); 396 return true; 397 }) 398 } 399} 400 401@Component 402struct pageTwoStack { 403 @Consume('pageInfo') pageInfo: NavPathStack; 404 @State index: number = 2; 405 @Link message: number; 406 @Link logNumber: number; 407 408 build() { 409 NavDestination() { 410 Column() { 411 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 412 Text("cur stack size:" + `${this.pageInfo.size()}`) 413 .fontSize(30) 414 .fontWeight(FontWeight.Bold) 415 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 416 .width('80%') 417 .height(40) 418 .margin(20) 419 .onClick(() => { 420 this.pageInfo.pushPathByName('pageThree', null); 421 }) 422 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 423 .width('80%') 424 .height(40) 425 .margin(20) 426 .onClick(() => { 427 this.pageInfo.pop(); 428 }) 429 }.width('100%').height('100%') 430 }.title('pageTwo') 431 .onBackPressed(() => { 432 this.pageInfo.pop(); 433 return true; 434 }) 435 } 436} 437 438@Component 439struct pageThreeStack { 440 @Consume('pageInfo') pageInfo: NavPathStack; 441 @State index: number = 3; 442 @Link message: number; 443 @Link logNumber: number; 444 445 build() { 446 NavDestination() { 447 Column() { 448 NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) 449 Text("cur stack size:" + `${this.pageInfo.size()}`) 450 .fontSize(30) 451 .fontWeight(FontWeight.Bold) 452 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 453 .width('80%') 454 .height(40) 455 .margin(20) 456 .onClick(() => { 457 this.pageInfo.pushPathByName('pageOne', null); 458 }) 459 Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) 460 .width('80%') 461 .height(40) 462 .margin(20) 463 .onClick(() => { 464 this.pageInfo.pop(); 465 }) 466 }.width('100%').height('100%') 467 }.title('pageThree') 468 .onBackPressed(() => { 469 this.pageInfo.pop(); 470 return true; 471 }) 472 } 473} 474 475@Component({ freezeWhenInactive: true }) 476struct NavigationContentMsgStack { 477 @Link @Watch("info") message: number; 478 @Link index: number; 479 @Link logNumber: number; 480 481 info() { 482 console.info(`freeze-test NavigationContent message callback ${this.message}`); 483 console.info(`freeze-test ---- called by content ${this.index}`); 484 this.logNumber++; 485 } 486 487 build() { 488 Column() { 489 Text("msg:" + `${this.message}`) 490 .fontSize(30) 491 .fontWeight(FontWeight.Bold) 492 Text("log number:" + `${this.logNumber}`) 493 .fontSize(30) 494 .fontWeight(FontWeight.Bold) 495 } 496 } 497} 498``` 499 500In the preceding example: 501 5021. When **change message** is clicked, the value of **message** changes, and the @Watch decorated **info** method of the **MyNavigationTestStack** component being displayed is called. 503 5042. When **Next Page** is clicked, **PageOne** is displayed, and the **PageOneStack** node is created. 505 5063. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageOneStack** is called. 507 5084. When **Next Page** is clicked again, **PageTwo** is displayed, and the **pageTwoStack** node is created. 509 5105. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageTwoStack** is called. 511 5126. When **Next Page** is clicked again, **PageThree** is displayed, and the **pageThreeStack** node is created. 513 5147. When **change message** is clicked again, the value of **message** changes, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageThreeStack** is called. 515 5168. When **Back Page** is clicked, **PageTwo** is displayed, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **pageTwoStack** is called. 517 5189. When **Back Page** is clicked again, **PageOne** is displayed, and only the @Watch decorated **info** method of the **NavigationContentMsgStack** child component in **PageOne** is called. 519 52010. When **Back Page** is clicked again, the initial page is displayed, and no method is called. 521 522 523