1# \@ReusableV2 Decorator: Reusing Components 2 3To reduce the performance overhead caused by repeated creation and destruction of custom components, you can use \@ReusableV2 to decorate \@ComponentV2 decorated custom components. 4 5Before reading this topic, you are advised to read [\@Reusable Decorator: Reusing Components](./arkts-reusable.md). 6 7>**NOTE** 8> 9>The \@ReusableV2 decorator is supported since API version 18. 10> 11 12## Overview 13 14Using \@ReusableV2 to decorate the custom components of V2 indicates that these components are reusable. 15 16- \@ReusableV2 can decorate only custom components of V2, that is, custom components decorated by \@ComponentV2. In addition, only \@ReusableV2 decorated custom components can be used as child components of custom components of V2. 17- \@ReusableV2 also provides the **aboutToRecycle** and **aboutToReuse** callbacks, which are called respectively when a component is recycled and reused. However, different from \@Reusable, **aboutToReuse** has no input parameters. 18- In the recycling phase, **aboutToRecycle** of all child components is recursively called (even if the child components are not reusable). In the reuse phase, so does **aboutToReuse** of all child components. 19- The custom component decorated by \@ReusableV2 remains frozen during recycling. That is, the UI r-render and the \@Monitor callback cannot be triggered. Different from the **freezeWhenInactive** flag, the delayed re-render is not triggered after the \@ReusableV2 decorated custom component is unfrozen. 20- When a \@ReusableV2 decorated custom component is reused, the value of its state variable is automatically reset, and the \@Computed and related \@Monitor in the component are recomputed. You are not advised to change the state variable of a component in **aboutToRecycle**. For details, see [Resetting State Variables in Components Before Reuse](#resetting-state-variables-in-components-before-reuse). 21- The reusable components of V1 and V2 can be used together under certain rules. For details, see the second point in [Constraints](#constraints). 22- Do not overuse the \@ReusableV2 decorator. Otherwise, the reuse efficiency may be undermined and the memory overhead may increase. 23 24## Decorator Description 25 26| \@ReusableV2 Decorator| Description | 27| ------------------ | ----------------------------- | 28| Parameters | None | 29| Decorable components | \@ComponentV2 decorated custom components| 30| Function | Indicates that a component is reusable. | 31 32```ts 33@ReusableV2 // Decorates @ComponentV2 decorated custom components. 34@ComponentV2 35struct ReusableV2Component { 36 @Local message: string = 'Hello World'; 37 build () { 38 Column() { 39 Text(this.message) 40 } 41 } 42} 43``` 44 45## Available APIs 46 47### ReuseIdCallback 48 49**ReuseIdCallback** calculates **reuseId**, whose type is **() => string**. 50 51### ReuseOptions 52 53**ReuseOptions** stores the **reuseId** information, which is used in the **.reuse** attribute. 54 55| Name | Type | Description | 56| ------- | --------------- | --------------------- | 57| reuseId | ReuseIdCallback | Callback used to obtain **reuseId**.| 58 59### The reuse Attribute 60 61The **reuse** attribute is used to specify a reuse ID for the custom component decorated by \@ReusableV2. Custom components with the same reuse ID will be reused. If reuse ID is not specified, the custom component name is used as reuse ID. 62 63| Parameter | Type | Description | 64| ------- | ------------ | ------------------------------------------------------ | 65| options | ReuseOptions | Configuration options for **reuse**, for example, **{reuseId: () => 'reuseComponent'}**.| 66 67```ts 68@Entry 69@ComponentV2 70struct Index { 71 build() { 72 Column() { 73 ReusableV2Component() 74 .reuse({reuseId: () => 'reuseComponent'}) // Use 'reuseComponent' as reuseId. 75 ReusableV2Component() 76 .reuse({reuseId: () => ''}) // If an empty string is used, the component name 'ReusableV2Component' is used as reuseId by default. 77 ReusableV2Component() // If reuseId is not specified, the component name 'ReusableV2Component' is used as reuseId by default. 78 } 79 } 80} 81@ReusableV2 82@ComponentV2 83struct ReusableV2Component { 84 build() { 85 } 86} 87``` 88 89## Constraints 90 91- Only \@ReusableV2 decorated custom components can be used as child components of custom components of V2. If a reusable component of V2 is used in the custom components of V1, an error is reported during compilation. In complex scenarios where verification cannot be performed during compilation, an error is reported during runtime. 92 93 ```ts 94 @Entry 95 @ComponentV2 96 struct Index { 97 build() { 98 Column() { 99 ReusableV2Component() // Correct usage. 100 V1Component() 101 } 102 } 103 } 104 @ReusableV2 105 @ComponentV2 106 struct ReusableV2Component { 107 build() { 108 } 109 } 110 @Builder 111 function V2ReusableBuilder() { 112 ReusableV2Component() 113 } 114 @Component 115 struct V1Component { 116 build() { 117 Column() { 118 ReusableV2Component() // Incorrect usage. An error is reported during compilation. 119 V2ReusableBuilder() // Incorrect usage. An error is reported during runtime in complex scenarios. 120 } 121 } 122 } 123 ``` 124 125- The following describes some mixed use cases of V1 and V2. 126 127 The mapping between a component and its type is listed in the table below. 128 129 | Component | Type | 130 | ---------- | ------------------------------------ | 131 | Common component of V1| @Component decorated struct | 132 | Common component of V2| @ComponentV2 decorated struct | 133 | Reusable component of V1| @Reusable and @Component decorated struct | 134 | Reusable component of V2| @ReusableV2 and @ComponentV2 decorated struct| 135 136 The following table shows the mixed use support between V1 and V2, indicating whether components in the second to fifth columns can be used as child components of the parent components in the first column. 137 138 For example, the common component of V1, common component of V2, and reusable component of V1 can be used as child components of the common component of V1 except the reusable component of V2. 139 140 | | Common Component of V1| Common Component of V2| Reusable Component of V1 | Reusable Component of V2 | 141 | ---------- | :--------: | :--------: | :------------------------------------: | :--------------: | 142 | Common component of V1| Yes | Yes | Yes | No. An error is reported during compilation.| 143 | Common component of V2| Yes | Yes | No. An error is reported during compilation. The actual used child component is not created.| Yes | 144 | Reusable component of V1| Yes | Yes | Yes | No. An error is reported during compilation.| 145 | Reusable component of V2| Yes | Yes | No. An error is reported during compilation. | Yes | 146 147 According to the preceding table, only 12 possible parent-child relationships are supported. You are advised not to highly nest reusable components, because it will reduce the reuse efficiency. 148 149- The reusable components of V2 cannot be directly used in the **template** of **Repeat**, but can be used in the custom component of V2 in **template**. 150 151 ```ts 152 @Entry 153 @ComponentV2 154 struct Index { 155 @Local arr: number[] = [1, 2, 3, 4, 5]; 156 build() { 157 Column() { 158 List() { 159 Repeat(this.arr) 160 .each(() => {}) 161 .virtualScroll() 162 .templateId(() => 'a') 163 .template('a', (ri) => { 164 ListItem() { 165 Column() { 166 ReusableV2Component({ val: ri.item}) // Usage not supported. An error is reported during compilation. 167 ReusableV2Builder(ri.item) // Usage not supported. An error is reported during runtime. 168 NormalV2Component({ val: ri.item}) // Reusable components of V2 can be used in common custom components of V2. 169 } 170 } 171 }) 172 } 173 } 174 } 175 } 176 @ComponentV2 177 struct NormalV2Component { 178 @Require @Param val: number; 179 build() { 180 ReusableV2Component({ val: this.val }) 181 } 182 } 183 @Builder 184 function ReusableV2Builder(param: number) { 185 ReusableV2Component({ val: param }) 186 } 187 @ReusableV2 188 @ComponentV2 189 struct ReusableV2Component { 190 @Require @Param val: number; 191 build() { 192 Column() { 193 Text(`val: ${this.val}`) 194 } 195 } 196 } 197 ``` 198 199## Lifecycle of Recycling and Reuse 200 201\@ReusableV2 provides the **aboutToRecycle** and **aboutToReuse** callbacks, which are called respectively when a component is recycled and reused. 202 203The following uses the **if** statement as an example: 204 205```ts 206@Entry 207@ComponentV2 208struct Index { 209 @Local condition1: boolean = false; 210 @Local condition2: boolean = true; 211 build() { 212 Column() { 213 Button('step1. appear') 214 .onClick(() => { 215 this.condition1 = true; 216 }) 217 Button('step2. recycle') 218 .onClick(() => { 219 this.condition2 = false; 220 }) 221 Button('step3. reuse') 222 .onClick(() => { 223 this.condition2 = true; 224 }) 225 Button('step4. disappear') 226 .onClick(() => { 227 this.condition1 = false; 228 }) 229 if (this.condition1) { 230 NormalV2Component({ condition: this.condition2 }) 231 } 232 } 233 } 234} 235@ComponentV2 236struct NormalV2Component { 237 @Require @Param condition: boolean; 238 build() { 239 if (this.condition) { 240 ReusableV2Component() 241 } 242 } 243} 244@ReusableV2 245@ComponentV2 246struct ReusableV2Component { 247 aboutToAppear() { 248 console.log('ReusableV2Component aboutToAppear called'); // Called when a component is created. 249 } 250 aboutToDisappear() { 251 console.log('ReusableV2Component aboutToDisappear called'); // Called when a component is destroyed. 252 } 253 aboutToRecycle() { 254 console.log('ReusableV2Component aboutToRecycle called'); // Called when a component is recycled. 255 } 256 aboutToReuse() { 257 console.log('ReusableV2Component aboutToReuse called'); // Called when a component is reused. 258 } 259 build() { 260 Column() { 261 Text('ReusableV2Component') 262 } 263 } 264} 265``` 266 267You are advised to follow the steps below: 268 2691. Click **step1. appear** to change the value of **condition1** to **true**. The **if** component in **Index** switches the branch and creates a **NormalV2Component**. Due to the initial value **true** of **condition2**, the **if** condition in **NormalV2Component** is met and the system attempts to create a **ReusableV2Component**. In this case, there is no element in the reuse pool. Therefore, **ReusableV2Component** is created, the **aboutToAppear** method is called back, and the log **ReusableV2Component aboutToAppear called** is output. 2702. Click **step2. recycle** to change the value of **condition2** to **false** and synchronize this change to **NormalV2Component** through \@Param. The **if** condition is switched. Because **ReusableV2Component** uses \@ReusableV2, the component is recycled to the reuse pool instead of being destroyed. The **aboutToRecycle** method is called back and the log **ReusableV2Component aboutToRecycle called** is output. 2713. Click **step3. reuse** to change the value of **condition2** to **true** and synchronize this change to **NormalV2Component** through \@Param. The **if** condition is switched. Because **ReusableV2Component** uses \@ReusableV2, the system attempts to search for the component in the reuse pool when creating the component. In this case, the component recycled in step 2 is reused from the reuse pool, the **aboutToReuse** method is called back, and the log **ReusableV2Component aboutToReuse called** is output. 2724. Click **step4. disappear** to change the value of **condition1** to **false**. The **if** component in **Index** switches the branch and destroys **NormalV2Component**. In this case, **ReusableV2Component** is destroyed due to the destroyed parent component, the **aboutToDisappear** method is called back, and the log **ReusableV2Component aboutToDisappear called** is output. 273 274If the reusable component has child components, **aboutToRecycle** and **aboutToReuse** of the child components are recursively called during recycling and reuse (irrelevant to whether the child components are marked for reuse) until all child components are traversed. 275 276## Component Freezing in the Reuse Phase 277 278In the previous reuse, components of V1 can still respond to updates in the reuse pool, which has a negative impact on performance. Therefore, you need to use the component freezing capability to resolve this issue. For this, V2 components are automatically frozen when reused and do not respond to changes that occur during recycling. For example, changes in **aboutToRecycle** are not updated to the UI, and \@Computed and \@Monitor are not triggered. The frozen state lasts until **aboutToReuse** and subsequent variables change. In this case, the UI re-render, \@Computed recalculation, and \@Monitor are triggered. 279 280The following uses the **if** statement as an example: 281 282```ts 283@ObservedV2 284class Info { 285 @Trace age: number = 25; 286} 287const info: Info = new Info(); 288@Entry 289@ComponentV2 290struct Index { 291 @Local condition: boolean = true; 292 build() { 293 Column() { 294 Button('Reuse/Recycle').onClick(()=>{this.condition=!this.condition;}) 295 Button('Change value').onClick(()=>{info.age++;}) 296 if (this.condition) { 297 ReusableV2Component() 298 } 299 } 300 } 301} 302@ReusableV2 303@ComponentV2 304struct ReusableV2Component { 305 @Local info: Info = info; // This is used only for demonstration. You are not advised to assign a value to the global variable using @Local. 306 @Monitor('info.age') 307 onValChange() { 308 console.log('info.age change'); 309 } 310 aboutToRecycle() { 311 console.log('aboutToRecycle'); 312 this.info.age++; 313 } 314 aboutToReuse() { 315 console.log('aboutToReuse'); 316 this.info.age++; 317 } 318 onRender(): string { 319 console.log('info.age onRender') 320 return this.info.age.toString(); 321 } 322 build() { 323 Column() { 324 Text(this.onRender()) 325 } 326 } 327} 328``` 329 330You are advised to follow the steps below: 331 3321. Click the **Change value** button to listen for the UI change. \@Monitor is triggered to output the logs **info.age change** and **info.age onRender**, indicating that the change can be listened for and the UI re-render can be triggered. 3332. Click the **Reuse/Recycle** button to call the **aboutToRecycle** method and output the log of **aboutToRecycle**. However, \@Monitor is not triggered and the **onRender** method is not called back. 3343. Click the **Change value** button. The UI is not re-rendered, \@Monitor is not triggered, and the **onRender** method is not called back. 3354. Click the **Reuse/Recycle** button. The UI is re-rendered, \@Monitor is trigger to output the log **info.age change**, and the **onRender** method is called back to output the log **info.age onRender**. 336 337If the auto-increment operation is removed from the **aboutToReuse** method, the \@Monitor callback is not triggered in step 4. 338 339In complex mixed use scenarios, the rules for component freezing can be summarized as follows: 340 3411. For Components of V1, component freezing is determined by **freezeWhenInactive**. 3422. V2 components are automatically frozen. 343 344## Resetting State Variables in Components Before Reuse 345 346Different from \@Reusable, \@ReusableV2 resets the state variables and related \@Computed and \@Monitor content in components before reuse. During the reuse, all custom components of V2 are reset no matter whether they are decorated with \@ReusableV2. 347 348The reset is performed based on the sequence of variables defined in the component according to the following rules. 349 350| Decorator | Method for Resetting | 351| ---------- | ------------------------------------------------------------ | 352| \@Local | Use the initial value for reassignment. | 353| \@Param | Use external input value for reassignment. If there is no external input value, use the local initial value. Note that variables decorated by \@Once are also reset and initialized.| 354| \@Event | Use external input value for reassignment. If there is no external input value, use the local initial value. If there is no local initial value, the default empty implementation is generated.| 355| \@Provider | Use the initial value for reassignment. | 356| \@Consumer | If the corresponding \@Provider exists, use the value of \@Provider. Otherwise, use the local initial value for reassignment.| 357| \@Computed | The latest value is used for recalculation. If the used variable is not reset, the value before the reset is used. Therefore, you are advised to define \@Computed after the used variable.| 358| \@Monitor | Triggered after all the preceding variables are reset. The variable change generated during the reset does not trigger the \@Monitor callback. Only the **before** value in **IMonitorValue** is updated. Values that do not change during the reset do not trigger the reset of \@Monitor.| 359| Constant | Read-only constants are included and are not reset. | 360 361The following example shows some of the effects: 362 363```ts 364@ObservedV2 365class Info { 366 @Trace age: number; 367 constructor(age: number) { 368 this.age = age; 369 } 370} 371@Entry 372@ComponentV2 373struct Index { 374 @Local local: number = 0; 375 @Provider('inherit') inheritProvider: number = 100; 376 @Local condition: boolean = true; 377 build() { 378 Column() { 379 Button('Recycle/Reuse').onClick(()=>{this.condition=!this.condition;}) 380 Column() { 381 Text('Variables of parent component') 382 Text(`local: ${this.local}`).onClick(()=>{this.local++;}) 383 Text(`inheritProvider: ${this.inheritProvider}`).onClick(()=>{this.inheritProvider++;}) 384 }.borderWidth(2) 385 if (this.condition) { 386 ReusableV2Component({ 387 paramOut: this.local, 388 paramOnce: this.local, 389 changeParam: () => { 390 this.local++; 391 } 392 }) 393 } 394 } 395 } 396} 397@ReusableV2 398@ComponentV2 399struct ReusableV2Component { 400 @Local val: number = 0; 401 @Local info: Info = new Info(25); 402 @Param paramLocal: number = 1; 403 @Require @Param paramOut: number; 404 @Require @Param @Once paramOnce: number; 405 @Event changeParam: () => void; 406 @Provider('selfProvider') selfProvider: number = 0; 407 @Consumer('inherit') inheritConsumer: number = 0; 408 @Consumer('selfConsumer') selfConsumer: number = 0; 409 noDecoVariable: number = 0; // No decorator is used. It is used as a constant. 410 noDecoInfo: Info = new Info(30); // No decorator is used. It is used as a constant. 411 readonly readOnlyVariable: number = 0; // Read-only constant. 412 @Computed 413 get plusParam() { 414 return this.paramLocal + this.paramOut + this.paramOnce; 415 } 416 @Monitor('val') 417 onValChange(monitor: IMonitor) { 418 console.log(`val change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 419 } 420 @Monitor('plusParam') 421 onPlusParamChange(monitor: IMonitor) { 422 console.log(`plusParam change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 423 } 424 build() { 425 Column() { 426 Column() { 427 Text('Variables reset to local initial values') 428 Text(`val: ${this.val}`).onClick(()=>{this.val++;}) 429 Text(`info.age: ${this.info.age}`).onClick(()=>{this.info.age++;}) 430 Text(`paramLocal: ${this.paramLocal}`).onClick(()=>{/* Local without external input value fails to be changed locally. */}) 431 Text(`selfProvider: ${this.selfProvider}`).onClick(()=>{this.selfProvider++;}) 432 Text(`selfConsumer: ${this.selfConsumer}`).onClick(()=>{this.selfConsumer++;}) 433 }.borderWidth(2) 434 Column() { 435 Text('Reset to an external variable') 436 Text(`paramOut: ${this.paramOut}`).onClick(()=>{this.changeParam();}) 437 Text(`paramOnce: ${this.paramOnce}`).onClick(()=>{this.paramOnce++;}) 438 }.borderWidth(2) 439 Column() { 440 Text ('Depending on the parent component') 441 Text(`inheritConsumer: ${this.inheritConsumer}`).onClick(()=>{this.inheritConsumer++;}) 442 Text(`plusParam: ${this.plusParam}`) 443 }.borderWidth(2) 444 Column() { 445 Text('Not reset') 446 Text(`noDecoVariable: ${this.noDecoVariable}`) 447 Text(`noDecoInfo.age: ${this.noDecoInfo.age}`).onClick(()=>{this.noDecoInfo.age++;}) // Update can be triggered but the variable is not reset when component is reused. 448 Text(`readOnlyVariable: ${this.readOnlyVariable}`) 449 }.borderWidth(2) 450 } 451 } 452} 453``` 454 455You can click each variable and click **Recycle/Reuse** to view the reset state after reuse. 456 457In the preceding example, **noDecoInfo** is not reset. If a \@Monitor is used to listen for **noDecoInfo.age**, it will not be reset because **noDecoInfo** does not change. Therefore, when **noDecoInfo.age** is changed for the first time, the **before** value of **IMonitorValue** will not be reset, which is still the value before reuse. 458 459The simplified example is as follows: 460 461```ts 462@ObservedV2 463class Info { 464 @Trace age: number; 465 constructor(age: number) { 466 this.age = age; 467 } 468} 469@Entry 470@ComponentV2 471struct Index { 472 @Local condition: boolean = true; 473 build() { 474 Column() { 475 Button('Recycle/Reuse').onClick(()=>{this.condition=!this.condition;}) 476 if (this.condition) { 477 ReusableV2Component() 478 } 479 } 480 } 481} 482@ReusableV2 483@ComponentV2 484struct ReusableV2Component { 485 noDecoInfo: Info = new Info(30); // No decorator is used. It is used as a constant. 486 @Monitor('noDecoInfo.age') 487 onAgeChange(monitor: IMonitor) { 488 console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`); 489 } 490 aboutToRecycle() { 491 this.noDecoInfo.age = 25; 492 } 493 aboutToReuse() { 494 this.noDecoInfo.age = 35; 495 } 496 build() { 497 Column() { 498 Column() { 499 Text(`noDecoInfo.age: ${this.noDecoInfo.age}`) 500 .onClick(()=>{this.noDecoInfo.age++;}) // Update can be triggered but the variable is not reset. 501 } 502 } 503 } 504} 505``` 506 507You are advised to follow the steps below: 508 5091. Click **noDecoInfo.age: 30**. The UI is re-rendered to **noDecoInfo.age: 31** and \@Monitor is triggered to output the log **age change from 30 to 31**. 5102. Click **Recycle/Reuse** twice. The UI is re-rendered to **noDecoInfo.age: 35** and \@Monitor is triggered to output the log **age change from 31 to 35**. 5113. Click **noDecoInfo.age: 35**. The UI is re-rendered to **noDecoInfo.age: 36** and \@Monitor is triggered to output the log **age change from 35 to 36**. 512 513Due to the freezing mechanism, the value change in **aboutToRecycle** is not listened by \@Monitor. After the variable is reset, a new value is assigned to the variable. Therefore, for the state variable in the component, the value assignment in **aboutToRecycle** does not have obvious effect. For the constant (such as **noDecoInfo**), due to the freezing mechanism, the change of **age** in **aboutToRecycle** is not observable and cannot be reset, therefore, the \@Monitor bound to **age** will not be reset. Finally, in \@Monitor called back in step 2, the value of **monitor.value()?.before** is **31** instead of the initial value **30** of **age**. 514 515As such, you are advised to reduce the use of constant objects that contain the \@Trace attribute in reuse scenarios to ensure that the reuse meets expectations. 516 517## When to Use 518 519### Using in the if Component 520 521Change the conditions of the **if** component to control component recycling or reuse. 522 523```ts 524@Entry 525@ComponentV2 526struct Index { 527 @Local condition: boolean = true; 528 build() { 529 Column() { 530 Button('Recycle/Reuse').onClick(()=>{this.condition=!this.condition;}) // Click to switch the recycle/reuse state. 531 if (this.condition) { 532 ReusableV2Component() 533 } 534 } 535 } 536} 537@ReusableV2 538@ComponentV2 539struct ReusableV2Component { 540 @Local message: string = 'Hello World'; 541 aboutToRecycle() { 542 console.log('ReusableV2Component aboutToRecycle'); // Called when a component is recycled. 543 } 544 aboutToReuse() { 545 console.log('ReusableV2Component aboutToReuse'); // Called when a component is reused. 546 } 547 build() { 548 Column() { 549 Text(this.message) 550 } 551 } 552} 553``` 554 555### Using in the each Attribute of Repeat in the Non-virtualScroll Scenarios 556 557In the non-virtualScroll scenarios of the **Repeat** component, recycling or reuse is triggered when a subtree is deleted or created. 558 559```ts 560@Entry 561@ComponentV2 562struct Index { 563 @Local simpleList: number[] = [1, 2, 3, 4, 5]; 564 @Local condition: boolean = true; 565 build() { 566 Column() { 567 Button('Delete/Create Repeat').onClick(()=>{this.condition=!this.condition;}) 568 Button('Add element').onClick(()=>{this.simpleList.push(this.simpleList.length+1);}) 569 Button('Delete element').onClick(()=>{this.simpleList.pop();}) 570 Button('Change element').onClick(()=>{this.simpleList[0]++;}) 571 if (this.condition) { 572 List({ space: 10 }) { 573 Repeat(this.simpleList) 574 .each((obj: RepeatItem<number>) => { 575 ListItem() { 576 Column() { 577 ReusableV2Component({ num: obj.item }) 578 } 579 } 580 }) 581 } 582 } 583 } 584 } 585} 586@ReusableV2 587@ComponentV2 588struct ReusableV2Component { 589 @Require @Param num: number; 590 aboutToAppear() { 591 console.log('ReusableV2Component aboutToAppear'); 592 } 593 aboutToRecycle() { 594 console.log('ReusableV2Component aboutToRecycle'); 595 } 596 aboutToReuse() { 597 console.log('ReusableV2Component aboutToReuse'); 598 } 599 build() { 600 Column() { 601 Text(`${this.num}`) 602 } 603 } 604} 605``` 606 607### Using in the each Attribute of Repeat in the virtualScroll Scenarios 608 609In the virtualScroll scenarios of the **Repeat** component, the cache pool of **Repeat** is preferentially used. In the normal sliding and update scenarios, component recycling and reuse are not involved. When the cache pool needs to be expanded, new child components are required from the custom components. In this case, if there are reusable nodes in the reuse pool, the nodes will be reused. 610 611In the following example, click **Change condition** to add three nodes to the reuse pool. When you slide down the **List** component, you can see the log output **ReusableV2Component aboutToReuse**, indicating that **Repeat** can use the reuse pool of the custom component to fill its cache pool. 612 613```ts 614@Entry 615@ComponentV2 616struct Index { 617 @Local condition: boolean = true; 618 @Local simpleList: number[] = []; 619 aboutToAppear(): void { 620 for (let i = 0; i < 100; i++) { 621 this.simpleList.push(i) 622 } 623 } 624 build() { 625 Column() { 626 Button('Change condition').onClick(()=>{this.condition=!this.condition;}) 627 if (this.condition) { 628 // This is only for demonstration. Fill three components in the reuse pool. 629 ReusableV2Component({ num: 0}) 630 ReusableV2Component({ num: 0}) 631 ReusableV2Component({ num: 0}) 632 } 633 List({ space: 10 }) { 634 Repeat(this.simpleList) 635 .virtualScroll() 636 .each((obj: RepeatItem<number>) => { 637 ListItem() { 638 Column() { 639 ReusableV2Component({ num: obj.item }) 640 } 641 } 642 }) 643 }.height('50%') 644 .cachedCount(2) 645 } 646 } 647} 648@ReusableV2 649@ComponentV2 650struct ReusableV2Component { 651 @Require @Param num: number; 652 aboutToAppear() { 653 console.log('ReusableV2Component aboutToAppear'); 654 } 655 aboutToRecycle() { 656 console.log('ReusableV2Component aboutToRecycle'); 657 } 658 aboutToReuse() { 659 console.log('ReusableV2Component aboutToReuse'); 660 } 661 build() { 662 Column() { 663 Text(`${this.num}`).fontSize(50) 664 } 665 } 666} 667``` 668 669### Using in ForEach 670>**NOTE** 671> 672>You are advised to use the non-virtualScroll scenarios of **Repeat** to replace **ForEach**. 673 674In the following example, ForEach is used to render multiple reusable components. Each time the **Click to change** button is clicked, the key value changes. Therefore, recycling and reuse are triggered from the second click. (When ForEach determines whether there is reusable nodes, the reuse pool is not initialized. Therefore, when ForEach is clicked for the first time, a new node is created. Then, the reuse pool is initialized from the second click and the node is recycled at the same time.) 675 676```ts 677@Entry 678@ComponentV2 679struct Index { 680 @Local simpleList: number[] = [0, 1, 2, 3, 4, 5]; 681 build() { 682 Column() { 683 ForEach(this.simpleList, (num: number, index) => { 684 Row() { 685 Button('Click to change').onClick (()=>{this.simpleList[index]++;}) 686 ReusableV2Component({ num: num }) 687 } 688 }) // The key changes from each click. 689 } 690 } 691} 692@ReusableV2 693@ComponentV2 694struct ReusableV2Component { 695 @Require @Param num: number; 696 aboutToAppear() { 697 console.log('ReusableV2Component aboutToAppear', this.num); // Triggered when the component is created. 698 } 699 aboutToRecycle() { 700 console.log('ReusableV2Component aboutToRecycle', this.num); // Triggered when the component is recycled. 701 } 702 aboutToReuse() { 703 console.log('ReusableV2Component aboutToReuse', this.num); // Triggered when the component is reused. 704 } 705 build() { 706 Column() { 707 Text(`child: ${this.num}`) 708 } 709 } 710} 711``` 712 713 714### Using in LazyForEach 715>**NOTE** 716> 717>You are advised to use the virtualScroll scenarios of **Repeat** to replace **LazyForEach**. 718 719In the following example, **LazyForEach** is used to render several reusable components. During sliding, component creation can be observed first. After all pre-loaded nodes are created, sliding triggers reuse and recycling. 720 721```ts 722class BasicDataSource implements IDataSource { 723 private listeners: DataChangeListener[] = []; 724 private originDataArray: StringData[] = []; 725 726 public totalCount(): number { 727 return 0; 728 } 729 730 public getData(index: number): StringData { 731 return this.originDataArray[index]; 732 } 733 734 registerDataChangeListener(listener: DataChangeListener): void { 735 if (this.listeners.indexOf(listener) < 0) { 736 console.info('add listener'); 737 this.listeners.push(listener); 738 } 739 } 740 741 unregisterDataChangeListener(listener: DataChangeListener): void { 742 const pos = this.listeners.indexOf(listener); 743 if (pos >= 0) { 744 console.info('remove listener'); 745 this.listeners.splice(pos, 1); 746 } 747 } 748 749 notifyDataReload(): void { 750 this.listeners.forEach(listener => { 751 listener.onDataReloaded(); 752 }) 753 } 754 755 notifyDataAdd(index: number): void { 756 this.listeners.forEach(listener => { 757 listener.onDataAdd(index); 758 }) 759 } 760 761 notifyDataChange(index: number): void { 762 this.listeners.forEach(listener => { 763 listener.onDataChange(index); 764 }) 765 } 766 767 notifyDataDelete(index: number): void { 768 this.listeners.forEach(listener => { 769 listener.onDataDelete(index); 770 }) 771 } 772 773 notifyDataMove(from: number, to: number): void { 774 this.listeners.forEach(listener => { 775 listener.onDataMove(from, to); 776 }) 777 } 778 779 notifyDatasetChange(operations: DataOperation[]): void { 780 this.listeners.forEach(listener => { 781 listener.onDatasetChange(operations); 782 }) 783 } 784} 785 786class MyDataSource extends BasicDataSource { 787 private dataArray: StringData[] = []; 788 789 public totalCount(): number { 790 return this.dataArray.length; 791 } 792 793 public getData(index: number): StringData { 794 return this.dataArray[index]; 795 } 796 797 public addData(index: number, data: StringData): void { 798 this.dataArray.splice(index, 0, data); 799 this.notifyDataAdd(index); 800 } 801 802 public pushData(data: StringData): void { 803 this.dataArray.push(data); 804 this.notifyDataAdd(this.dataArray.length - 1); 805 } 806} 807 808@ObservedV2 809class StringData { 810 @Trace message: string; 811 constructor(message: string) { 812 this.message = message; 813 } 814} 815 816@Entry 817@ComponentV2 818struct Index { 819 data: MyDataSource = new MyDataSource(); // Data source. 820 821 aboutToAppear() { 822 for (let i = 0; i <= 200; i++) { 823 this.data.pushData(new StringData('Hello' + i)); 824 } 825 } 826 build() { 827 List({ space: 3 }) { 828 LazyForEach(this.data, (item: StringData, index: number) => { 829 ListItem() { 830 Column() { 831 Text(item.message) 832 ChildComponent({ data: item.message }) 833 .onClick(() => { 834 item.message += '!'; // message is a variable decorated by @Trace and its change is observable. 835 }) 836 } 837 } 838 }) 839 }.cachedCount(5) 840 } 841} 842 843@ReusableV2 844@ComponentV2 845struct ChildComponent { 846 @Param @Require data: string; 847 aboutToAppear(): void { 848 console.log('ChildComponent aboutToAppear', this.data) 849 } 850 aboutToDisappear(): void { 851 console.log('ChildComponent aboutToDisappear', this.data) 852 } 853 aboutToReuse(): void { 854 console.log('ChildComponent aboutToReuse', this.data) // Triggered when the component is reused. 855 } 856 aboutToRecycle(): void { 857 console.log('ChildComponent aboutToRecycle', this.data) // Triggered when the component is recycled. 858 } 859 build() { 860 Row() { 861 Text(this.data).fontSize(50) 862 } 863 } 864} 865``` 866