• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![Repeat-Render](./figures/Repeat-Render.png)
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![Repeat-Example-With-Each](./figures/Repeat-Example-With-Each.png)
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![Repeat-Example-With-Templates](./figures/Repeat-Example-With-Templates.png)
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![Repeat-Start](./figures/Repeat-Start.PNG)
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![Repeat-Slide](./figures/Repeat-Slide.PNG)
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![Repeat-Update1](./figures/Repeat-Update1.PNG)
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![Repeat-Update2](./figures/Repeat-Update2.PNG)
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![Repeat-Lazyloading-1](./figures/repeat-lazyloading-demo1.gif)
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![Repeat-Lazyloading-2](./figures/repeat-lazyloading-demo2.gif)
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![Repeat-Lazyloading-3](./figures/repeat-lazyloading-demo3.gif)
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![Repeat-Drag-Sort](./figures/repeat-drag-sort.gif)
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![Repeat-pre-insert-preserve](./figures/repeat-pre-insert-preserve.gif)
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![Repeat-VirtualScroll-2T-Demo](./figures/Repeat-VirtualScroll-2T-Demo.gif)
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![Repeat-Nest](./figures/Repeat-Nest.png)
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![Repeat-Demo-List](./figures/Repeat-Demo-List.gif)
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![Repeat-Demo-Grid](./figures/Repeat-Demo-Grid.gif)
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![Repeat-Demo-Swiper](./figures/Repeat-Demo-Swiper.gif)
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![Repeat-NonVS-FuncGen](./figures/Repeat-NonVS-FuncGen.png)
1042
1043In the example of array changes shown below, item_*X* represents the key of a data item.
1044
1045![Repeat-NonVS-Example](./figures/Repeat-NonVS-Example.png)
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![ForEach-Non-Initial-Render-Case-Effect](./figures/ForEach-Non-Initial-Render-Case-Effect.gif)
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![repeat-case1-wrong](./figures/repeat-case1-wrong.gif)
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![repeat-case1-fixed](./figures/repeat-case1-fixed.gif)
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![Repeat-Case2-Succ](./figures/Repeat-Case2-Succ.gif)
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![Repeat-Builder](./figures/Repeat-Builder.png)
1428