1# Repeat: Reusing Components for Repeated Content Rendering 2 3> **NOTE** 4> 5> **Repeat** is supported since API version 12. 6> 7> This topic serves as a development guide. For details about the component API specifications, see [Repeat](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md). 8 9## Overview 10 11**Repeat** performs iterative rendering based on array data and is typically used together with container components. 12 13**Repeat** loads child components based on the parent container's effective loading range (visible area + preload area). When scrolling occurs or the array data changes, **Repeat** dynamically recalculates the loading range based on the container's layout process, while managing the creation and destruction of child component nodes. By efficiently updating or reusing component nodes, **Repeat** improves rendering performance. For details, see [Node Update and Reuse Mechanism](#node-update-and-reuse-mechanism). 14 15> **NOTE** 16> 17> Differences between **Repeat** and [LazyForEach](./arkts-rendering-control-lazyforeach.md): 18> - **Repeat** directly listens for state variable changes, whereas **LazyForEach** requires developers to implement the [IDataSource](../../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md#idatasource) API and manually manage changes to the content and indexes of child components. 19> - **Repeat** enhances node reuse, improving rendering performance for long list scrolling and data updates. 20> - **Repeat** supports rendering templates, enabling rendering of different child components within the same array based on custom template types. 21 22## Constraints 23 24- **Repeat** must be used within the following scrollable container components: [List](../../reference/apis-arkui/arkui-ts/ts-container-list.md), [Grid](../../reference/apis-arkui/arkui-ts/ts-container-grid.md), [Swiper](../../reference/apis-arkui/arkui-ts/ts-container-swiper.md), [WaterFlow](../../reference/apis-arkui/arkui-ts/ts-container-waterflow.md). 25<br>Each iteration can only create one child component, which must be compatible with its parent container. For example, when **Repeat** is used with the [List](../../reference/apis-arkui/arkui-ts/ts-container-list.md) component, the child component must be [ListItem](../../reference/apis-arkui/arkui-ts/ts-container-listitem.md). 26- **Repeat** does not support V1 decorators. Using it with V1 decorators can cause rendering issues. 27- Currently, **Repeat** does not support animations. 28- A scrollable container component can contain only one **Repeat**. For example, in a **List** component, using **ListItem**, **ForEach**, and **LazyForEach** together, or using multiple **Repeat** instances, is not recommended. 29- When **Repeat** is used together with a custom component or [@Builder](./arkts-builder.md) function, the parameter of the **RepeatItem** type must be passed as a whole to the component for data changes to be detected. For details, see [Using Repeat with @Builder](#using-repeat-with-builder). 30 31> **NOTE** 32> 33> The functionality of **Repeat** depends on dynamic modifications to array properties. If the array object is sealed or frozen, certain **Repeat** features may not function properly, as these operations prevent property extensions or lock existing property configurations. 34> 35> Common scenarios that may trigger this issue:<br>1. Observable data conversion: When a regular array (such as [collections.Array](../../reference/apis-arkts/js-apis-arkts-collections.md#collectionsarray)) is converted into observable data using [makeObserved](../../reference/apis-arkui/js-apis-StateManagement.md#makeobserved), some implementations may automatically seal the array.<br>2. Intentional object protection: explicit calls to **Object.seal()** or **Object.freeze()** to prevent array modifications. 36 37## How It Works 38 39Child components of **Repeat** are defined using **.each()** and .**template()** properties, with only one child component allowed per instance. During initial page rendering, **Repeat** creates child components on demand based on the current effective loading range (visible area + preload area), as illustrated below. 40 41 42 43**.each()** applies to the scenario where only one type of child component needs to be rendered in an iteration. The following example demonstrates basic usage of **Repeat**: 44 45```ts 46// Use Repeat in a List container component. 47@Entry 48@ComponentV2 // The V2 decorator is recommended. 49struct RepeatExample { 50 @Local dataArr: Array<string> = []; // Data source. 51 52 aboutToAppear(): void { 53 for (let i = 0; i < 50; i++) { 54 this.dataArr.push(`data_${i}`); // Add data to the array. 55 } 56 } 57 58 build() { 59 Column() { 60 List() { 61 Repeat<string>(this.dataArr) 62 .each((ri: RepeatItem<string>) => { 63 ListItem() { 64 Text('each_' + ri.item).fontSize(30) 65 } 66 }) 67 .virtualScroll({ totalCount: this.dataArr.length }) // Enable lazy loading. totalCount indicates the data length to be loaded. 68 } 69 .cachedCount(2) // Size of the preload area. 70 .height('70%') 71 .border({ width: 1 }) // Border. 72 } 73 } 74} 75``` 76 77After execution, the UI is displayed as shown below. 78 79 80 81**Repeat** supports rendering templates, enabling multiple types of child components to be rendered from a single data source. Each data item obtains its template type through the **.templateId()** API, and the corresponding **.template()** API is used to render the appropriate child component. 82 83- If **.templateId()** is not provided, the default type, which is an empty string, is used. 84- If multiple **.template()** APIs share the same type, the last defined one takes effect, overriding the previous ones. 85- If no matching template type is found, the child component in **.template()** whose type is an empty string is rendered first. If no such **.template()** is available, the child component in **.each()** is rendered. 86- Only nodes with the same template type can be reused. 87 88The following example demonstrates how to use **Repeat** with multiple rendering templates. 89 90```ts 91// Use Repeat in a List container component. 92@Entry 93@ComponentV2 // The V2 decorator is recommended. 94struct RepeatExampleWithTemplates { 95 @Local dataArr: Array<string> = []; // Data source. 96 97 aboutToAppear(): void { 98 for (let i = 0; i < 50; i++) { 99 this.dataArr.push(`data_${i}`); // Add data to the array. 100 } 101 } 102 103 build() { 104 Column() { 105 List() { 106 Repeat<string>(this.dataArr) 107 .each((ri: RepeatItem<string>) => { // Default rendering template. 108 ListItem() { 109 Text('each_' + ri.item).fontSize(30).fontColor('rgb(161,10,33)') // The font color is red. 110 } 111 }) 112 .key((item: string, index: number): string => JSON.stringify(item)) // Key generator. 113 .virtualScroll({ totalCount: this.dataArr.length }) // Enable lazy loading. totalCount indicates the data length to be loaded. 114 .templateId((item: string, index: number): string => { // Search for the corresponding template child component for rendering based on the return value. 115 return index <= 4 ? 'A' : (index <= 10 ? 'B' : ''); // The first five nodes use template A, the next five nodes use template B, and the others use the default template. 116 }) 117 .template('A', (ri: RepeatItem<string>) => { // Template A. 118 ListItem() { 119 Text('A_' + ri.item).fontSize(30).fontColor('rgb(23,169,141)') // The font color is green. 120 } 121 }, { cachedCount: 3 }) // Cache capacity of template A: 3 instances. 122 .template('B', (ri: RepeatItem<string>) => { // Template B. 123 ListItem() { 124 Text('B_' + ri.item).fontSize(30).fontColor('rgb(39,135,217)') // The font color is blue. 125 } 126 }, { cachedCount: 4 }) // Cache capacity of template B: 4 instances. 127 } 128 .cachedCount(2) // Size of the preload area. 129 .height('70%') 130 .border({ width: 1 }) // Border. 131 } 132 } 133} 134``` 135 136After execution, the UI is displayed as shown below. 137 138 139 140## Node Update and Reuse Mechanism 141 142> **NOTE** 143> 144> **Repeat** handles child components through four operations: creation, update, reuse, and destruction. The difference between node update and node reuse is as follows: 145> 146> - Node update: The node is not destroyed, and its properties are updated based on changes to state variables. 147> - Node reuse: The old node is not destroyed but moved to the idle node cache pool. When a new node is needed, **Repeat** obtains a reusable node from the cache pool and updates its properties accordingly. 148 149When scrolling occurs or the array data changes, **Repeat** moves child nodes that fall outside the effective loading range to the cache pool. These nodes are disconnected from the page component tree but not destroyed. When new components are needed, nodes from the cache pool are reused. 150 151By default, node reuse is enabled for **Repeat**. Since API version 18, you can configure the **reusable** field to specify whether to enable node reuse. For better rendering performance, you are advised to keep node reuse enabled. For a code example, see [VirtualScrollOptions](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#virtualscrolloptions). 152 153Since API version 18, **Repeat** supports L2 caching of frozen custom components. For details, see [Freezing Custom Components in a Buffer Pool](./arkts-custom-components-freezeV2.md#repeat). 154 155The following illustrates the rendering logic of child components under typical [scroll](#scrolling-scenario) and [data update](#data-update-scenario) scenarios. In the figure below, the L1 cache represents the effective loading area managed by **Repeat**, and the L2 cache refers to the idle node cache pool for each rendering template. 156 157For this example, we define an array with 20 items. The first 5 items use template type **'aa'**, while the remaining items use template type **'bb'**. The cache pool capacity is set to 3 nodes for template **'aa'** and 4 nodes for template **'bb'**. The size of the preload area of the container component is 2. For demonstration purposes, one idle node is added to the **aa** cache pool, and two in the **bb** cache pool. 158 159The following figure shows the list node status after initial rendering. 160 161 162 163### Scrolling Scenario 164 165When the user swipes the screen to the right by the distance of one node, **Repeat** starts to reuse nodes from the cache pool. The node whose index is 10 enters the effective loading area, and its template type is identified as **bb**. Because the **bb** cache pool is not empty, **Repeat** obtains an idle node from this pool for reuse and updates the node's properties. The child component's descendant components involving data items and indexes are updated synchronously based on V2 state management rules. Other nodes within the effective loading area only require index updates. 166 167The node whose index is 0 moves out of the effective loading area. When the UI main thread is idle, the system checks whether the **aa** cache pool is full. If it is not full, the system adds the node to the corresponding cache pool; otherwise, **Repeat** destroys redundant nodes. 168 169 170 171### Data Update Scenario 172 173Perform the following array update operations based on the previous section: Delete the node whose index is 4 and change **item_7** to **new_7**. 174 175After the node whose index is 4 is deleted, it is invalidated and added to the **aa** cache pool. The subsequent nodes move leftwards, with the newly entering **item_11** node reusing an idle node in the **bb** cache pool, while other nodes only receive index updates. 176 177 178 179Then, as the **item_5** node moves leftwards and its index is updated to 4, its template type changes to **aa** according to calculation rules. This requires reusing an idle node from the **aa** cache pool and adding the old node back to the **bb** cache pool. 180 181 182 183## Key Generation 184 185The **.key()** property of **Repeat** generates a unique key for each child component. These keys enable **Repeat** to identify added and removed data items and track positional changes (index movements) within the array. 186 187> **NOTE** 188> 189> Differences between a key and an index: A key is a unique data identifier, which can be used to determine whether a data item has changed, while an index simply indicates a data item's position in the array. 190 191If **.key()** is not specified, **Repeat** auto-generates random keys. If a duplicate key is found, **Repeat** recursively generates a new key based on the existing one until no duplicate key exists. 192 193When using **.key()**, pay attention to the following: 194 195- Even if the array changes, you must ensure that keys remain unique across all items in the array. 196- The **.key()** function must return a consistent key for the same data item across all executions. 197- While technically allowed, using **index** in **.key()** is discouraged. Indexes change when items are added, removed, or rearranged, causing keys to shift and forcing **Repeat** to re-create components, which degrades performance. 198- (Recommended) Convert simple-type arrays into class object arrays with a **readonly id** property initialized using a unique value. 199 200## Precise Lazy Loading 201 202When the total length of the data source is long or loading data items takes time, you can use the precise lazy loading feature of **Repeat** to avoid loading all data during initialization. 203 204You can set the **totalCount** property of **.virtualScroll()** or the custom **onTotalCount** method to calculate the expected length of the data source, and set the **onLazyLoading** property to implement precise lazy loading of data. This way, the corresponding data is loaded when the node is rendered for the first time. For details, see [VirtualScrollOptions](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#virtualscrolloptions). 205 206**Example 1** 207 208This example demonstrates how to dynamically load data in the corresponding area during the initial rendering, screen scrolling, and display area navigation for scenarios where the total length of the data source is long. 209 210```ts 211@Entry 212@ComponentV2 213struct RepeatLazyLoading { 214 // Assume that the total length of the data source is 1000. The initial array does not provide data. 215 @Local arr: Array<string> = []; 216 scroller: Scroller = new Scroller(); 217 build() { 218 Column({ space: 5 }) { 219 // The initial item displayed on the screen is at index 100. Data can be automatically obtained through lazy loading. 220 List({ scroller: this.scroller, space: 5, initialIndex: 100 }) { 221 Repeat(this.arr) 222 .virtualScroll({ 223 // The expected total length of the data source is 1000. 224 onTotalCount: () => { return 1000; }, 225 // Implement lazy loading. 226 onLazyLoading: (index: number) => { this.arr[index] = index.toString(); } 227 }) 228 .each((obj: RepeatItem<string>) => { 229 ListItem() { 230 Row({ space: 5 }) { 231 Text(`${obj.index}: Item_${obj.item}`) 232 } 233 } 234 .height(50) 235 }) 236 } 237 .height('80%') 238 .border({ width: 1}) 239 // Navigate to the position at index 500. Data can be automatically obtained through lazy loading. 240 Button('ScrollToIndex 500') 241 .onClick(() => { this.scroller.scrollToIndex(500); }) 242 } 243 } 244} 245``` 246 247The figure below shows the effect. 248 249 250 251**Example 2** 252 253This example deals with time-consuming data loading. In the **onLazyLoading** method, placeholders are created for data items, and then data is loaded through asynchronous tasks. 254 255```ts 256@Entry 257@ComponentV2 258struct RepeatLazyLoading { 259 @Local arr: Array<string> = []; 260 build() { 261 Column({ space: 5 }) { 262 List({ space: 5 }) { 263 Repeat(this.arr) 264 .virtualScroll({ 265 onTotalCount: () => { return 100; }, 266 // Implement lazy loading. 267 onLazyLoading: (index: number) => { 268 // Create a placeholder. 269 this.arr[index] = ''; 270 // Simulate a time-consuming loading process and load data through an asynchronous task. 271 setTimeout(() => { this.arr[index] = index.toString(); }, 1000); 272 } 273 }) 274 .each((obj: RepeatItem<string>) => { 275 ListItem() { 276 Row({ space: 5 }) { 277 Text(`${obj.index}: Item_${obj.item}`) 278 } 279 } 280 .height(50) 281 }) 282 } 283 .height('100%') 284 .border({ width: 1}) 285 } 286 } 287} 288``` 289 290The figure below shows the effect. 291 292 293 294**Example 3** 295 296This example shows how to implement infinite lazy loading of data by using lazy loading together with **onTotalCount: () => { return this.arr.length + 1; }**. 297 298> **NOTE** 299> 300> - In this scenario, you need to provide the initial data required for the first screen display and set **cachedCount** to a value greater than 0 for the parent container component. Otherwise, rendering exceptions will occur. 301> - Avoid using the **onLazyLoading** method together with the loop mode of **Swipe**. Otherwise, staying at **index = 0** will trigger continuous **onLazyLoading** calls. 302> - Pay special attention to the memory usage to avoid excessive memory consumption caused by continuous data loading. 303 304```ts 305@Entry 306@ComponentV2 307struct RepeatLazyLoading { 308 @Local arr: Array<string> = []; 309 // Provide the initial data required for the first screen display. 310 aboutToAppear(): void { 311 for (let i = 0; i < 15; i++) { 312 this.arr.push(i.toString()); 313 } 314 } 315 build() { 316 Column({ space: 5 }) { 317 List({ space: 5 }) { 318 Repeat(this.arr) 319 .virtualScroll({ 320 // Enable infinite lazy loading of data. 321 onTotalCount: () => { return this.arr.length + 1; }, 322 onLazyLoading: (index: number) => { this.arr[index] = index.toString(); } 323 }) 324 .each((obj: RepeatItem<string>) => { 325 ListItem() { 326 Row({ space: 5 }) { 327 Text(`${obj.index}: Item_${obj.item}`) 328 } 329 } 330 .height(50) 331 }) 332 } 333 .height('100%') 334 .border({ width: 1}) 335 // You are advised to set cachedCount to a value greater than 0. 336 .cachedCount(1) 337 } 338 } 339} 340``` 341 342The figure below shows the effect. 343 344 345 346 347## Drag-and-Drop Sorting 348 349By using **Repeat** within a **List** component and setting up the [onMove](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-sorting.md#onmove) event, you can implement drag-and-drop sorting. The drag-and-drop sorting feature is supported since API version 19. 350 351> **NOTE** 352> 353> - When the drag-and-drop gesture is released, if any item's position changes, the **onMove** event is triggered, which reports the original index and target index of the relocated item.<br>In the **onMove** event, the data source must be updated based on the reported start index and target index. Before and after the data source is modified, the key value of each item must remain unchanged to ensure that the drop animation can be executed properly. 354> - Modify data source only after the drag-and-drop operation is completed. 355 356**Example** 357 358```ts 359@Entry 360@ComponentV2 361struct RepeatVirtualScrollOnMove { 362 @Local simpleList: Array<string> = []; 363 364 aboutToAppear(): void { 365 for (let i = 0; i < 100; i++) { 366 this.simpleList.push(`${i}`); 367 } 368 } 369 370 build() { 371 Column() { 372 List() { 373 Repeat<string>(this.simpleList) 374 // Set onMove to enable drag-and-drop sorting. 375 .onMove((from: number, to: number) => { 376 let temp = this.simpleList.splice(from, 1); 377 this.simpleList.splice(to, 0, temp[0]); 378 }) 379 .each((obj: RepeatItem<string>) => { 380 ListItem() { 381 Text(obj.item) 382 .fontSize(16) 383 .textAlign(TextAlign.Center) 384 .size({height: 100, width: "100%"}) 385 }.margin(10) 386 .borderRadius(10) 387 .backgroundColor("#FFFFFFFF") 388 }) 389 .key((item: string, index: number) => { 390 return item; 391 }) 392 .virtualScroll({ totalCount: this.simpleList.length }) 393 } 394 .border({ width: 1 }) 395 .backgroundColor("#FFDCDCDC") 396 .width('100%') 397 .height('100%') 398 } 399 } 400} 401``` 402 403The figure below shows the effect. 404 405 406 407## Content Position Preservation 408 409The content position preservation feature, introduced in API version 20, maintains visible component positions when data is inserted or deleted before the current viewport area. 410 411For this feature to work, the parent container must be a **List** component and the [maintainVisibleContentPosition](../../reference/apis-arkui/arkui-ts/ts-container-list.md#maintainvisiblecontentposition12) attribute must be set to **true**. 412 413**Example** 414 415```ts 416@Entry 417@ComponentV2 418struct PreInsertDemo { 419 @Local simpleList: Array<string> = []; 420 private cnt: number = 1; 421 422 aboutToAppear(): void { 423 for (let i = 0; i < 30; i++) { 424 this.simpleList.push(`Hello ${this.cnt++}`); 425 } 426 } 427 428 build() { 429 Column() { 430 Row() { 431 Button(`insert #5`) 432 .onClick(() => { 433 this.simpleList.splice(5, 0, `Hello ${this.cnt++}`); 434 }) 435 Button(`delete #0`) 436 .onClick(() => { 437 this.simpleList.splice(0, 1); 438 }) 439 } 440 441 List({ initialIndex: 5 }) { 442 Repeat<string>(this.simpleList) 443 .each((obj: RepeatItem<string>) => { 444 ListItem() { 445 Row() { 446 Text(`index: ${obj.index} `) 447 .fontSize(16) 448 .fontColor("#70707070") 449 .textAlign(TextAlign.End) 450 .size({ height: 100, width: "40%" }) 451 Text(`item: ${obj.item}`) 452 .fontSize(16) 453 .textAlign(TextAlign.Start) 454 .size({ height: 100, width: "60%" }) 455 } 456 }.margin(10) 457 .borderRadius(10) 458 .backgroundColor("#FFFFFFFF") 459 }) 460 .key((item: string, index: number) => item) 461 .virtualScroll({ totalCount: this.simpleList.length }) 462 } 463 .maintainVisibleContentPosition(true) // Enable content position preservation. 464 .border({ width: 1 }) 465 .backgroundColor("#FFDCDCDC") 466 .width('100%') 467 .height('100%') 468 } 469 } 470} 471``` 472 473In the example, insertions or deletions above the viewport cause only index updates, while displayed items remain visually stable. 474 475The figure below shows the effect. 476 477 478 479## Use Cases 480 481### Data Display and Operations 482 483The following sample code shows how to insert, modify, delete, and swap data items in an array using **Repeat**. Select an index from the drop-down list box and click the corresponding button to operate the data item. Click two data items in sequence to swap them. 484 485```ts 486@ObservedV2 487class Repeat006Clazz { 488 @Trace message: string = ''; 489 490 constructor(message: string) { 491 this.message = message; 492 } 493} 494 495@Entry 496@ComponentV2 497struct RepeatVirtualScroll2T { 498 @Local simpleList: Array<Repeat006Clazz> = []; 499 private exchange: number[] = []; 500 private counter: number = 0; 501 @Local selectOptions: SelectOption[] = []; 502 @Local selectIdx: number = 0; 503 504 @Monitor("simpleList") 505 reloadSelectOptions(): void { 506 this.selectOptions = []; 507 for (let i = 0; i < this.simpleList.length; ++i) { 508 this.selectOptions.push({ value: i.toString() }); 509 } 510 if (this.selectIdx >= this.simpleList.length) { 511 this.selectIdx = this.simpleList.length - 1; 512 } 513 } 514 515 aboutToAppear(): void { 516 for (let i = 0; i < 100; i++) { 517 this.simpleList.push(new Repeat006Clazz(`item_${i}`)); 518 } 519 this.reloadSelectOptions(); 520 } 521 522 handleExchange(idx: number): void { // Click to swap child components. 523 this.exchange.push(idx); 524 if (this.exchange.length === 2) { 525 let _a = this.exchange[0]; 526 let _b = this.exchange[1]; 527 let temp: Repeat006Clazz = this.simpleList[_a]; 528 this.simpleList[_a] = this.simpleList[_b]; 529 this.simpleList[_b] = temp; 530 this.exchange = []; 531 } 532 } 533 534 build() { 535 Column({ space: 10 }) { 536 Text('virtualScroll each()&template() 2t') 537 .fontSize(15) 538 .fontColor(Color.Gray) 539 Text('Select an index and press the button to update data.') 540 .fontSize(15) 541 .fontColor(Color.Gray) 542 543 Select(this.selectOptions) 544 .selected(this.selectIdx) 545 .value(this.selectIdx.toString()) 546 .key('selectIdx') 547 .onSelect((index: number) => { 548 this.selectIdx = index; 549 }) 550 Row({ space: 5 }) { 551 Button('Add No.' + this.selectIdx) 552 .onClick(() => { 553 this.simpleList.splice(this.selectIdx, 0, new Repeat006Clazz(`${this.counter++}_add_item`)); 554 this.reloadSelectOptions(); 555 }) 556 Button('Modify No.' + this.selectIdx) 557 .onClick(() => { 558 this.simpleList.splice(this.selectIdx, 1, new Repeat006Clazz(`${this.counter++}_modify_item`)); 559 }) 560 Button('Del No.' + this.selectIdx) 561 .onClick(() => { 562 this.simpleList.splice(this.selectIdx, 1); 563 this.reloadSelectOptions(); 564 }) 565 } 566 Button('Update array length to 5.') 567 .onClick(() => { 568 this.simpleList = this.simpleList.slice(0, 5); 569 this.reloadSelectOptions(); 570 }) 571 572 Text('Click on two items to exchange.') 573 .fontSize(15) 574 .fontColor(Color.Gray) 575 576 List({ space: 10 }) { 577 Repeat<Repeat006Clazz>(this.simpleList) 578 .each((obj: RepeatItem<Repeat006Clazz>) => { 579 ListItem() { 580 Text(`[each] index${obj.index}: ${obj.item.message}`) 581 .fontSize(25) 582 .onClick(() => { 583 this.handleExchange(obj.index); 584 }) 585 } 586 }) 587 .key((item: Repeat006Clazz, index: number) => { 588 return item.message; 589 }) 590 .virtualScroll({ totalCount: this.simpleList.length }) 591 .templateId((item: Repeat006Clazz, index: number) => { 592 return (index % 2 === 0) ? 'odd' : 'even'; 593 }) 594 .template('odd', (ri) => { 595 Text(`[odd] index${ri.index}: ${ri.item.message}`) 596 .fontSize(25) 597 .fontColor(Color.Blue) 598 .onClick(() => { 599 this.handleExchange(ri.index); 600 }) 601 }, { cachedCount: 3 }) 602 .template('even', (ri) => { 603 Text(`[even] index${ri.index}: ${ri.item.message}`) 604 .fontSize(25) 605 .fontColor(Color.Green) 606 .onClick(() => { 607 this.handleExchange(ri.index); 608 }) 609 }, { cachedCount: 1 }) 610 } 611 .cachedCount(2) 612 .border({ width: 1 }) 613 .width('95%') 614 .height('40%') 615 } 616 .justifyContent(FlexAlign.Center) 617 .width('100%') 618 .height('100%') 619 } 620} 621``` 622 623This example demonstrates the implementation of 100 items using a custom class **RepeatClazz** with a string property **message**. The **cachedCount** attribute of the **List** component is set to **2**, and the sizes of the idle node cache pools for the **'odd'** and **'even'** templates are set to **3** and **1**, respectively. After execution, the UI is displayed as shown below. 624 625 626 627### Repeat Nesting 628 629**Repeat** supports nesting. The sample code is as follows: 630 631```ts 632// Repeat can be nested in other components. 633@Entry 634@ComponentV2 635struct RepeatNest { 636 @Local outerList: string[] = []; 637 @Local innerList: number[] = []; 638 639 aboutToAppear(): void { 640 for (let i = 0; i < 20; i++) { 641 this.outerList.push(i.toString()); 642 this.innerList.push(i); 643 } 644 } 645 646 build() { 647 Column({ space: 20 }) { 648 Text('Nested Repeat with virtualScroll') 649 .fontSize(15) 650 .fontColor(Color.Gray) 651 List() { 652 Repeat<string>(this.outerList) 653 .each((obj) => { 654 ListItem() { 655 Column() { 656 Text('outerList item: ' + obj.item) 657 .fontSize(30) 658 List() { 659 Repeat<number>(this.innerList) 660 .each((subObj) => { 661 ListItem() { 662 Text('innerList item: ' + subObj.item) 663 .fontSize(20) 664 } 665 }) 666 .key((item) => "innerList_" + item) 667 .virtualScroll() 668 } 669 .width('80%') 670 .border({ width: 1 }) 671 .backgroundColor(Color.Orange) 672 } 673 .height('30%') 674 .backgroundColor(Color.Pink) 675 } 676 .border({ width: 1 }) 677 }) 678 .key((item) => "outerList_" + item) 679 .virtualScroll() 680 } 681 .width('80%') 682 .border({ width: 1 }) 683 } 684 .justifyContent(FlexAlign.Center) 685 .width('90%') 686 .height('80%') 687 } 688} 689``` 690 691The figure below shows the effect. 692 693 694 695### Integration with Parent Container Components 696 697This section provides examples of using **Repeat** within scrollable container components. 698 699#### Using with a List Component 700 701This example demonstrates how to use **Repeat** in the **List** component. 702 703```ts 704class DemoListItemInfo { 705 name: string; 706 icon: Resource; 707 708 constructor(name: string, icon: Resource) { 709 this.name = name; 710 this.icon = icon; 711 } 712} 713 714@Entry 715@ComponentV2 716struct DemoList { 717 @Local videoList: Array<DemoListItemInfo> = []; 718 719 aboutToAppear(): void { 720 for (let i = 0; i < 10; i++) { 721 // app.media.listItem0, app.media.listItem1, and app.media.listItem2 are only examples. Replace them with the actual ones in use. 722 this.videoList.push(new DemoListItemInfo('Video' + i, 723 i % 3 == 0 ? $r("app.media.listItem0") : 724 i % 3 == 1 ? $r("app.media.listItem1") : $r("app.media.listItem2"))); 725 } 726 } 727 728 @Builder 729 itemEnd(index: number) { 730 Button('Delete') 731 .backgroundColor(Color.Red) 732 .onClick(() => { 733 this.videoList.splice(index, 1); 734 }) 735 } 736 737 build() { 738 Column({ space: 10 }) { 739 Text('List Contains the Repeat Component') 740 .fontSize(15) 741 .fontColor(Color.Gray) 742 743 List({ space: 5 }) { 744 Repeat<DemoListItemInfo>(this.videoList) 745 .each((obj: RepeatItem<DemoListItemInfo>) => { 746 ListItem() { 747 Column() { 748 Image(obj.item.icon) 749 .width('80%') 750 .margin(10) 751 Text(obj.item.name) 752 .fontSize(20) 753 } 754 } 755 .swipeAction({ 756 end: { 757 builder: () => { 758 this.itemEnd(obj.index); 759 } 760 } 761 }) 762 .onAppear(() => { 763 console.info('AceTag', obj.item.name); 764 }) 765 }) 766 .key((item: DemoListItemInfo) => item.name) 767 .virtualScroll() 768 } 769 .cachedCount(2) 770 .height('90%') 771 .border({ width: 1 }) 772 .listDirection(Axis.Vertical) 773 .alignListItem(ListItemAlign.Center) 774 .divider({ 775 strokeWidth: 1, 776 startMargin: 60, 777 endMargin: 60, 778 color: '#ffe9f0f0' 779 }) 780 781 Row({ space: 10 }) { 782 Button('Delete No.1') 783 .onClick(() => { 784 this.videoList.splice(0, 1); 785 }) 786 Button('Delete No.5') 787 .onClick(() => { 788 this.videoList.splice(4, 1); 789 }) 790 } 791 } 792 .width('100%') 793 .height('100%') 794 .justifyContent(FlexAlign.Center) 795 } 796} 797``` 798 799Swipe left and touch the **Delete** button, or touch the button at the bottom to delete the video widget. 800 801 802 803#### Using with a Grid Component 804 805This example demonstrates how to use **Repeat** in the **Grid** component. 806 807```ts 808class DemoGridItemInfo { 809 name: string; 810 icon: Resource; 811 812 constructor(name: string, icon: Resource) { 813 this.name = name; 814 this.icon = icon; 815 } 816} 817 818@Entry 819@ComponentV2 820struct DemoGrid { 821 @Local itemList: Array<DemoGridItemInfo> = []; 822 @Local isRefreshing: boolean = false; 823 private layoutOptions: GridLayoutOptions = { 824 regularSize: [1, 1], 825 irregularIndexes: [10] 826 } 827 private GridScroller: Scroller = new Scroller(); 828 private num: number = 0; 829 830 aboutToAppear(): void { 831 for (let i = 0; i < 10; i++) { 832 // app.media.gridItem0, app.media.gridItem1, and app.media.gridItem2 are only examples. Replace them with the actual ones in use. 833 this.itemList.push(new DemoGridItemInfo('Video' + i, 834 i % 3 == 0 ? $r("app.media.gridItem0") : 835 i % 3 == 1 ? $r("app.media.gridItem1") : $r("app.media.gridItem2"))); 836 } 837 } 838 839 build() { 840 Column({ space: 10 }) { 841 Text('Grid Contains the Repeat Component') 842 .fontSize(15) 843 .fontColor(Color.Gray) 844 845 Refresh({ refreshing: $$this.isRefreshing }) { 846 Grid(this.GridScroller, this.layoutOptions) { 847 Repeat<DemoGridItemInfo>(this.itemList) 848 .each((obj: RepeatItem<DemoGridItemInfo>) => { 849 if (obj.index === 10 ) { 850 GridItem() { 851 Text('Last viewed here. Touch to refresh.') 852 .fontSize(20) 853 } 854 .height(30) 855 .border({ width: 1 }) 856 .onClick(() => { 857 this.GridScroller.scrollToIndex(0); 858 this.isRefreshing = true; 859 }) 860 .onAppear(() => { 861 console.info('AceTag', obj.item.name); 862 }) 863 } else { 864 GridItem() { 865 Column() { 866 Image(obj.item.icon) 867 .width('100%') 868 .height(80) 869 .objectFit(ImageFit.Cover) 870 .borderRadius({ topLeft: 16, topRight: 16 }) 871 Text(obj.item.name) 872 .fontSize(15) 873 .height(20) 874 } 875 } 876 .height(100) 877 .borderRadius(16) 878 .backgroundColor(Color.White) 879 .onAppear(() => { 880 console.info('AceTag', obj.item.name); 881 }) 882 } 883 }) 884 .key((item: DemoGridItemInfo) => item.name) 885 .virtualScroll() 886 } 887 .columnsTemplate('repeat(auto-fit, 150)') 888 .cachedCount(4) 889 .rowsGap(15) 890 .columnsGap(10) 891 .height('100%') 892 .padding(10) 893 .backgroundColor('#F1F3F5') 894 } 895 .onRefreshing(() => { 896 setTimeout(() => { 897 this.itemList.splice(10, 1); 898 this.itemList.unshift(new DemoGridItemInfo('refresh', $r('app.media.gridItem0'))); // app.media.gridItem0 is only an example. Replace it with the actual one. 899 for (let i = 0; i < 10; i++) { 900 // app.media.gridItem0, app.media.gridItem1, and app.media.gridItem2 are only examples. Replace them with the actual ones in use. 901 this.itemList.unshift(new DemoGridItemInfo('New video ' + this.num, 902 i % 3 == 0 ? $r("app.media.gridItem0") : 903 i % 3 == 1 ? $r("app.media.gridItem1") : $r("app.media.gridItem2"))); 904 this.num++; 905 } 906 this.isRefreshing = false; 907 }, 1000); 908 console.info('AceTag', 'onRefreshing'); 909 }) 910 .refreshOffset(64) 911 .pullToRefresh(true) 912 .width('100%') 913 .height('85%') 914 915 Button('Refresh') 916 .onClick(() => { 917 this.GridScroller.scrollToIndex(0); 918 this.isRefreshing = true; 919 }) 920 } 921 .width('100%') 922 .height('100%') 923 .justifyContent(FlexAlign.Center) 924 } 925} 926``` 927 928Swipe down on the screen, touch the **Refresh** button, or touch **Last viewed here. Touch to refresh.** to load new videos. 929 930 931 932#### Using with a Swiper Component 933 934This example demonstrates how to use **Repeat** in the **Swiper** component. 935 936```ts 937const remotePictures: Array<string> = [ 938 'https://www.example.com/xxx/0001.jpg', // Set the specific network image address. 939 'https://www.example.com/xxx/0002.jpg', 940 'https://www.example.com/xxx/0003.jpg', 941 'https://www.example.com/xxx/0004.jpg', 942 'https://www.example.com/xxx/0005.jpg', 943 'https://www.example.com/xxx/0006.jpg', 944 'https://www.example.com/xxx/0007.jpg', 945 'https://www.example.com/xxx/0008.jpg', 946 'https://www.example.com/xxx/0009.jpg' 947]; 948 949@ObservedV2 950class DemoSwiperItemInfo { 951 id: string; 952 @Trace url: string = 'default'; 953 954 constructor(id: string) { 955 this.id = id; 956 } 957} 958 959@Entry 960@ComponentV2 961struct DemoSwiper { 962 @Local pics: Array<DemoSwiperItemInfo> = []; 963 964 aboutToAppear(): void { 965 for (let i = 0; i < 9; i++) { 966 this.pics.push(new DemoSwiperItemInfo('pic' + i)); 967 } 968 setTimeout(() => { 969 this.pics[0].url = remotePictures[0]; 970 }, 1000); 971 } 972 973 build() { 974 Column() { 975 Text('Swiper Contains the Repeat Component') 976 .fontSize(15) 977 .fontColor(Color.Gray) 978 979 Stack() { 980 Text('Loading...') 981 .fontSize(15) 982 .fontColor(Color.Gray) 983 Swiper() { 984 Repeat(this.pics) 985 .each((obj: RepeatItem<DemoSwiperItemInfo>) => { 986 Image(obj.item.url) 987 .onAppear(() => { 988 console.info('AceTag', obj.item.id); 989 }) 990 }) 991 .key((item: DemoSwiperItemInfo) => item.id) 992 .virtualScroll() 993 } 994 .cachedCount(9) 995 .height('50%') 996 .loop(false) 997 .indicator(true) 998 .onChange((index) => { 999 setTimeout(() => { 1000 this.pics[index].url = remotePictures[index]; 1001 }, 1000); 1002 }) 1003 } 1004 .width('100%') 1005 .height('100%') 1006 .backgroundColor(Color.Black) 1007 } 1008 } 1009} 1010``` 1011 1012Here network latency is simulated with a 1-second delay for image loading. 1013 1014 1015 1016## Lazy Loading Disablement 1017 1018For scenarios involving short lists or requiring immediate loading of all components, you can disable lazy loading in **Repeat** by omitting its **.virtualScroll()** property. In this case, **Repeat** renders all child components during initial page loading. For long lists (typically with more than 30 items), disabling lazy loading will cause **Repeat** to load all child components at once, which is time-consuming and not recommended. 1019 1020> **NOTE** 1021> 1022> - The rendering template feature is unavailable when lazy loading is disabled. 1023> - With lazy loading disabled, **Repeat** can be used in any container component. 1024> - This feature is compatible with V1 decorators. 1025> - With lazy loading disabled, UI updates are dependent on key value changes: If keys remain identical, the UI will not update even when underlying data changes. For details, see [Node Update Mechanism](#node-update-mechanism). 1026 1027### Node Update Mechanism 1028 1029When lazy loading is disabled, **Repeat** handles array changes as follows:<br>On initial renders, all child components are created. When the data array changes, **Repeat** executes the following steps: 1030 1031First, **Repeat** traverses old array keys. If it identifies keys absent in the new array, it adds them to the **deletedKeys** collection. 1032 1033Second, **Repeat** traverses new array keys. For each key in the new array: 1034 10351. If a match can be found in the old array, the corresponding child component node is reused, with its index updated. 10362. If no matches can be found in the old array and the **deletedKeys** collection is not empty, **Repeat** reuses the most recently deleted node according to the last in first out (LIFO) policy and updates its key and content. 10373. If no matches can be found in the old array and the **deletedKeys** collection is empty, **Repeat** creates a new node for the key. 1038 1039Third, after the new array keys are traversed, nodes corresponding to the remaining keys in the **deletedKeys** collection are destroyed. 1040 1041 1042 1043In the example of array changes shown below, item_*X* represents the key of a data item. 1044 1045 1046 1047Based on the aforementioned update logic, **item_0** remains unchanged, **item_1** and **item_2** only have their indexes changed, **item_n1** and **item_n2** are obtained by updating **item_4** and **item_3**, respectively, and **item_n3** is newly created because no reusable nodes are available. 1048 1049> **NOTE** 1050> 1051> Key differences between **Repeat** with lazy loading disabled and [ForEach](arkts-rendering-control-foreach.md): 1052> - Performance optimization: **Repeat** implements specialized rendering enhancements for array update scenarios. 1053> - Architectural shift: Component content and index management responsibilities are elevated to the framework level. 1054 1055### Example 1056 1057```ts 1058@Entry 1059@ComponentV2 1060struct Parent { 1061 @Local simpleList: Array<string> = ['one', 'two', 'three']; 1062 1063 build() { 1064 Row() { 1065 Column() { 1066 Text('Click to change the value of the third array item') 1067 .fontSize(24) 1068 .fontColor(Color.Red) 1069 .onClick(() => { 1070 this.simpleList[2] = 'new three'; 1071 }) 1072 1073 Repeat<string>(this.simpleList) 1074 .each((obj: RepeatItem<string>)=>{ 1075 ChildItem({ item: obj.item }) 1076 .margin({top: 20}) 1077 }) 1078 .key((item: string) => item) 1079 } 1080 .justifyContent(FlexAlign.Center) 1081 .width('100%') 1082 .height('100%') 1083 } 1084 .height('100%') 1085 .backgroundColor(0xF1F3F5) 1086 } 1087} 1088 1089@ComponentV2 1090struct ChildItem { 1091 @Param @Require item: string; 1092 1093 build() { 1094 Text(this.item) 1095 .fontSize(30) 1096 } 1097} 1098``` 1099 1100 1101 1102When the red text component is clicked, the third data item undergoes a content update while preserving its existing component node. 1103 1104## Implementation Notes 1105 1106### Maintaining the Scroll Position When Off-Screen Data Changes 1107 1108In the following example, changes to off-screen data affect the scroll position of the **List** component. 1109When a **Repeat** component is declared within a **List** component (as shown in the code below), clicking the insert button adds an element before the first visible item, causing the list to scroll downward unexpectedly. 1110 1111```ts 1112// Define a class and mark it as observable. 1113// Define a custom array in the class and mark it as traceable. 1114@ObservedV2 1115class ArrayHolder { 1116 @Trace arr: Array<number> = []; 1117 1118 // Constructor used to initialize the array length. 1119 constructor(count: number) { 1120 for (let i = 0; i < count; i++) { 1121 this.arr.push(i); 1122 } 1123 } 1124} 1125 1126@Entry 1127@ComponentV2 1128struct RepeatTemplateSingle { 1129 @Local arrayHolder: ArrayHolder = new ArrayHolder(100); 1130 @Local totalCount: number = this.arrayHolder.arr.length; 1131 scroller: Scroller = new Scroller(); 1132 1133 build() { 1134 Column({ space: 5 }) { 1135 List({ space: 20, initialIndex: 19, scroller: this.scroller }) { 1136 Repeat(this.arrayHolder.arr) 1137 .virtualScroll({ totalCount: this.totalCount }) 1138 .templateId((item, index) => { 1139 return 'number'; 1140 }) 1141 .template('number', (r) => { 1142 ListItem() { 1143 Text(r.index! + ":" + r.item + "Reuse"); 1144 } 1145 }) 1146 .each((r) => { 1147 ListItem() { 1148 Text(r.index! + ":" + r.item + "eachMessage"); 1149 } 1150 }) 1151 } 1152 .height('30%') 1153 1154 Button(`insert totalCount ${this.totalCount}`) 1155 .height(60) 1156 .onClick(() => { 1157 // Insert an element before the first visible element on screen. 1158 this.arrayHolder.arr.splice(18, 0, this.totalCount); 1159 this.totalCount = this.arrayHolder.arr.length; 1160 }) 1161 } 1162 .width('100%') 1163 .margin({ top: 5 }) 1164 } 1165} 1166``` 1167 1168The figure below shows the effect. 1169 1170 1171 1172**Implementation After Correction** 1173To maintain the scroll position during off-screen data changes, use the [onScrollIndex](../../ui/arkts-layout-development-create-list.md#handling-scroll-position-changes) callback of the **List** component to listen for scrolling and obtain the scroll position when the list scrolls. Then, use the [scrollToIndex](../../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scrolltoindex) API of **Scroller** to lock the scroll position when off-screen data is added or removed. 1174 1175The following example demonstrates handling of data additions: 1176 1177```ts 1178// The definition of ArrayHolder is the same as that in the demo code above. 1179 1180@Entry 1181@ComponentV2 1182struct RepeatTemplateSingle { 1183 @Local arrayHolder: ArrayHolder = new ArrayHolder(100); 1184 @Local totalCount: number = this.arrayHolder.arr.length; 1185 scroller: Scroller = new Scroller(); 1186 1187 private start: number = 1; 1188 private end: number = 1; 1189 1190 build() { 1191 Column({ space: 5 }) { 1192 List({ space: 20, initialIndex: 19, scroller: this.scroller }) { 1193 Repeat(this.arrayHolder.arr) 1194 .virtualScroll({ totalCount: this.totalCount }) 1195 .templateId((item, index) => { 1196 return 'number'; 1197 }) 1198 .template('number', (r) => { 1199 ListItem() { 1200 Text(r.index! + ":" + r.item + "Reuse") 1201 } 1202 }) 1203 .each((r) => { 1204 ListItem() { 1205 Text(r.index! + ":" + r.item + "eachMessage") 1206 } 1207 }) 1208 } 1209 .onScrollIndex((start, end) => { 1210 this.start = start; 1211 this.end = end; 1212 }) 1213 .height('30%') 1214 1215 Button(`insert totalCount ${this.totalCount}`) 1216 .height(60) 1217 .onClick(() => { 1218 // Insert an element before the first visible element on screen. 1219 this.arrayHolder.arr.splice(18, 0, this.totalCount); 1220 let rect = this.scroller.getItemRect(this.start); // Obtain the size and position of the child component. 1221 this.scroller.scrollToIndex(this.start + 1); // Scroll to the specified index. 1222 this.scroller.scrollBy(0, -rect.y); // Scroll by a specified distance. 1223 this.totalCount = this.arrayHolder.arr.length; 1224 }) 1225 } 1226 .width('100%') 1227 .margin({ top: 5 }) 1228 } 1229} 1230``` 1231 1232The figure below shows the effect. 1233 1234 1235 1236### Handling Cases Where the totalCount Value Exceeds the Data Source Length 1237 1238For large datasets, lazy loading is typically used to render only a portion of the data initially. For **Repeat** to display the correct scrollbar style, **totalCount** must be set to the expected total data length, which means the **totalCount** value may be greater than the **array.length** value before all data is loaded. 1239 1240If the **totalCount** value is greater than the **array.length** value, the application should request subsequent data when the list is about to reach the end of the currently loaded items. Implement safeguards for data request errors (for example, network latency) to prevent UI display anomalies. 1241 1242This can be implemented using the [onScrollIndex](../../ui/arkts-layout-development-create-list.md#handling-scroll-position-changes) callback of the parent component (**List** or **Grid**). The sample code is as follows: 1243 1244```ts 1245@ObservedV2 1246class VehicleData { 1247 @Trace name: string; 1248 @Trace price: number; 1249 1250 constructor(name: string, price: number) { 1251 this.name = name; 1252 this.price = price; 1253 } 1254} 1255 1256@ObservedV2 1257class VehicleDB { 1258 public vehicleItems: VehicleData[] = []; 1259 1260 constructor() { 1261 // The initial size of the array is 20. 1262 for (let i = 1; i <= 20; i++) { 1263 this.vehicleItems.push(new VehicleData(`Vehicle${i}`, i)); 1264 } 1265 } 1266} 1267 1268@Entry 1269@ComponentV2 1270struct entryCompSucc { 1271 @Local vehicleItems: VehicleData[] = new VehicleDB().vehicleItems; 1272 @Local listChildrenSize: ChildrenMainSize = new ChildrenMainSize(60); 1273 @Local totalCount: number = this.vehicleItems.length; 1274 scroller: Scroller = new Scroller(); 1275 1276 build() { 1277 Column({ space: 3 }) { 1278 List({ scroller: this.scroller }) { 1279 Repeat(this.vehicleItems) 1280 .virtualScroll({ totalCount: 50 }) // The expected array length is 50. 1281 .templateId(() => 'default') 1282 .template('default', (ri) => { 1283 ListItem() { 1284 Column() { 1285 Text(`${ri.item.name} + ${ri.index}`) 1286 .width('90%') 1287 .height(this.listChildrenSize.childDefaultSize) 1288 .backgroundColor(0xFFA07A) 1289 .textAlign(TextAlign.Center) 1290 .fontSize(20) 1291 .fontWeight(FontWeight.Bold) 1292 } 1293 }.border({ width: 1 }) 1294 }, { cachedCount: 5 }) 1295 .each((ri) => { 1296 ListItem() { 1297 Text("Wrong: " + `${ri.item.name} + ${ri.index}`) 1298 .width('90%') 1299 .height(this.listChildrenSize.childDefaultSize) 1300 .backgroundColor(0xFFA07A) 1301 .textAlign(TextAlign.Center) 1302 .fontSize(20) 1303 .fontWeight(FontWeight.Bold) 1304 }.border({ width: 1 }) 1305 }) 1306 .key((item, index) => `${index}:${item}`) 1307 } 1308 .height('50%') 1309 .margin({ top: 20 }) 1310 .childrenMainSize(this.listChildrenSize) 1311 .alignListItem(ListItemAlign.Center) 1312 .onScrollIndex((start, end) => { 1313 console.log('onScrollIndex', start, end); 1314 // Lazy loading 1315 if (this.vehicleItems.length < 50) { 1316 for (let i = 0; i < 10; i++) { 1317 if (this.vehicleItems.length < 50) { 1318 this.vehicleItems.push(new VehicleData("Vehicle_loaded", i)); 1319 } 1320 } 1321 } 1322 }) 1323 } 1324 } 1325} 1326``` 1327 1328The figure below shows the effect. 1329 1330 1331 1332### Using Repeat with @Builder 1333 1334When **Repeat** is used together with @Builder, the parameter of the **RepeatItem** type must be passed as a whole to the component for data changes to be detected. If only **RepeatItem.item** or **RepeatItem.index** is passed, UI rendering exceptions will occur. 1335 1336The sample code is as follows: 1337 1338```ts 1339@Entry 1340@ComponentV2 1341struct RepeatBuilderPage { 1342 @Local simpleList1: Array<number> = []; 1343 @Local simpleList2: Array<number> = []; 1344 1345 aboutToAppear(): void { 1346 for (let i = 0; i < 100; i++) { 1347 this.simpleList1.push(i); 1348 this.simpleList2.push(i); 1349 } 1350 } 1351 1352 build() { 1353 Column({ space: 20 }) { 1354 Text('Use Repeat and @Builder together: The abnormal display is on the left, and the normal display is on the right.') 1355 .fontSize(15) 1356 .fontColor(Color.Gray) 1357 1358 Row({ space: 20 }) { 1359 List({ initialIndex: 5, space: 20 }) { 1360 Repeat<number>(this.simpleList1) 1361 .each((ri) => {}) 1362 .virtualScroll({ totalCount: this.simpleList1.length }) 1363 .templateId((item: number, index: number) => "default") 1364 .template('default', (ri) => { 1365 ListItem() { 1366 Column() { 1367 Text('Text id = ' + ri.item) 1368 .fontSize(20) 1369 this.buildItem1 (ri.item) // Incorrect. To avoid rendering issues, change it to this.buildItem1(ri). 1370 } 1371 } 1372 .border({ width: 1 }) 1373 }, { cachedCount: 3 }) 1374 } 1375 .cachedCount(1) 1376 .border({ width: 1 }) 1377 .width('45%') 1378 .height('60%') 1379 1380 List({ initialIndex: 5, space: 20 }) { 1381 Repeat<number>(this.simpleList2) 1382 .each((ri) => {}) 1383 .virtualScroll({ totalCount: this.simpleList2.length }) 1384 .templateId((item: number, index: number) => "default") 1385 .template('default', (ri) => { 1386 ListItem() { 1387 Column() { 1388 Text('Text id = ' + ri.item) 1389 .fontSize(20) 1390 this.buildItem2(ri) // Correct. The rendering is normal. 1391 } 1392 } 1393 .border({ width: 1 }) 1394 }, { cachedCount: 3 }) 1395 } 1396 .cachedCount(1) 1397 .border({ width: 1 }) 1398 .width('45%') 1399 .height('60%') 1400 } 1401 } 1402 .height('100%') 1403 .justifyContent(FlexAlign.Center) 1404 } 1405 1406 @Builder 1407 // The @Builder parameter must be of the RepeatItem type for proper rendering. 1408 buildItem1(item: number) { 1409 Text('Builder1 id = ' + item) 1410 .fontSize(20) 1411 .fontColor(Color.Red) 1412 .margin({ top: 2 }) 1413 } 1414 1415 @Builder 1416 buildItem2(ri: RepeatItem<number>) { 1417 Text('Builder2 id = ' + ri.item) 1418 .fontSize(20) 1419 .fontColor(Color.Red) 1420 .margin({ top: 2 }) 1421 } 1422} 1423``` 1424 1425The following figure shows the display effect. Scroll down on the page to observe the difference: The left side demonstrates incorrect usage, while the right side shows the correct usage. (**Text** components are in black, while **Builder** components are in red). The preceding code shows the error-prone scenario during development, where only the value, instead the entire **RepeatItem** object, is passed in the @Builder function. 1426 1427 1428