• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Repeat: Reusing Child Components
2
3>**NOTE**
4>
5>Repeat is supported since API version 12.
6>
7>State management V2 is still under development, and some features may be incomplete or not always work as expected.
8
9For details about API parameters, see [Repeat APIs](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md).
10
11When **virtualScroll** is disabled, the **Repeat** component, which is used together with the container component, performs loop rendering based on array data. In addition, the component returned by the API should be a child component that can be contained in the **Repeat** parent container. Compared with ForEach, **Repeat** optimizes the rendering performance in some update scenarios and generates function with the index maintained by the framework.
12
13When **virtualScroll** is enabled, **Repeat** iterates data from the provided data source as required and creates the corresponding component during each iteration. When **Repeat** is used in the scrolling container, the framework creates components as required based on the visible area of the scrolling container. When a component slides out of the visible area, the framework caches the component and uses it in the next iteration.
14
15> **Note:**
16>
17> The **virtualScroll** scenario of the **Repeat** component is not fully compatible with the decorators in V1. Using decorators in V1 together with **virtualScroll** scenario may cause rendering exceptions.
18
19## Constraints
20
21- **Repeat** must be used in container components. Only the following components support virtual scrolling: [List](../reference/apis-arkui/arkui-ts/ts-container-list.md), [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md), [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md) and [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md). In this case, **cachedCount** takes effect. Do not enable the **virtualScroll** function when other container components are using the **Repeat** component.
22- After **virtualScroll** is enabled for **Repeat**, only one child component can be created in each iteration. Otherwise, there is no constraint.
23- The generated child components must be allowed in the parent container component of **Repeat**.
24- **Repeat** can be included in an **if/else** statement, and can also contain such a statement.
25- **Repeat** uses key as identifiers internally. Therefore, the key generator must generate a unique value for each piece of data. If the key generated for multiple pieces of data at the same time are the same, UI component rendering will be faulty.
26- If **virtualScroll** is disabled, the **template** is not supported currently and problems may occur when reusing.
27- When **Repeat** and **\@Builder** are used together, parameters of the **RepeatItem** type must be passed so that the component can listen for data changes. If only **RepeatItem.item** or **RepeatItem.index** is passed, UI rendering exceptions occur.
28- In the virtualScroll scenario, the value of totalCount is customized. When the length of data source changes, the value of totalCount should be manually updated. Otherwise, the rendering exception occurs on the list display area.
29
30## Key Generation Rules
31
32### non-virtualScroll
33
34![Repeat-Slide](./figures/Repeat-NonVirtualScroll-Key.png)
35
36### virtualScroll
37
38**virtualScroll** has a key generation rule similar to that of **non-virtualScroll.** However, it does not automatically handle the duplicate keys, so you need to ensure that the keys are unique.
39
40![Repeat-Slide](./figures/Repeat-VirtualScroll-Key.png)
41
42## Component Generation and Reuse Rules
43
44### non-virtualScroll
45
46All child components are created when **Repeat** is rendered for the first time. The original components are reused when data is updated.
47
48When the **Repeat** component updates data, it compares all keys in the last update with those in the latest update. If the current key is the same as the last one, **Repeat** reuses the child component and updates the **RepeatItem.index** index.
49
50After **Repeat** compares all duplicate keys and reuses them, if the last key is unique and a new key is generated after this update, a child component needs to be created. In this case, **Repeat** will reuse redundant child components, update the **RepeatItem.item** data source and **RepeatItem.index** index, and re-render the UI.
51
52If the number of remaining child components is greater than or equal to the number of newly updated components, the components are fully reused and redundant components are released. If the number of remaining child components is less than the number of newly updated components, **Repeat** will create components corresponding to the extra data items after the remaining data items are all reused.
53
54### virtualScroll
55
56At the first time when **Repeat** renders child components, only the required component is generated. During sliding and data update, nodes on the lower screen are cached. When a new component needs to be generated, the cached component is reused.
57
58#### Slide shortcut
59
60The following figure describes the node state before sliding.
61
62![Repeat-Start](./figures/Repeat-Start.png)
63
64Currently, the **Repeat** component has two types of templateId. **templateId a** sets three as its maximum cache value for the corresponding cache pool. **templateId b** sets four as its maximum cache value and preloads one note for its parent components by default. Now swipe right on the screen, and **Repeat** will reuse the nodes in the cache pool.
65
66![Repeat-Slide](./figures/Repeat-Slide.png)
67
68The data of **index=18** enters the screen and the preloading range of the parent component, coming up with a result of **templateId b**. In this case, **Repeat** obtains a node from the **type=b** cache pool for reuse and updates its key, index, and data. Other grandchildren notes that use the data and index in the child node are updated based on the state management V2 rules.
69
70The **index=10** note slides out of the screen and the preloading range of the parent component. When the UI main thread is idle, it checks whether the **type=a** cache pool has sufficient space. In this case, there are four nodes in the cache pool, which exceeds the rated three, so **Repeat** will release the last node.
71
72![Repeat-Slide-Done](./figures/Repeat-Slide-Done.png)
73
74#### Data Update Scenarios
75
76![Repeat-Start](./figures/Repeat-Start.png)
77
78In this case, delete the **index=12** node, update the data of the **index=13** node, change the **templateId b** to **templateId a** of the **index=14** node, and update the key of the **index=15** node.
79
80![Repeat-Update1](./figures/Repeat-Update1.png)
81
82Now, **Repeat** notifies the parent component to re-lay out the nodes and compares the keys one by one. If the template ID of the node is the same as that of the original one, the note is reused to update the **key**, **index** and **data**. Otherwise, the node in the cache pool with the same template ID is reused to update the **key**, **index**, and **data**.
83
84![Repeat-Update2](./figures/Repeat-Update2.png)
85
86As shown in the preceding figure, node13 updates **data** and **index**; node14 updates the template ID and **index** and reuses a node from the cache pool; node15 reuses its own node and updates the **key**, **index**, and **data** synchronously because of the changed **key** and the unchanged template ID; node 16 and node 17 only update the **index**. The **index=17** node is new and reused from the cache pool.
87
88![Repeat-Update-Done](./figures/Repeat-Update-Done.png)
89
90## cachedCount Rules
91
92The differences between the **.cachedCount** attribute of the the **List** or **Grid** component and the **cachedCount** attribute of the **Repeat** must be clarified. Both are used to balance performance and memory, but their definitions are different.
93- **.cachedCount** of **List** or **Grid**: indicates the nodes that are located in the component tree and treated as invisible. Container components such as **List** or **Grid** render these nodes to achieve better performance. But **Repeat** treats these nodes as visible.
94- template `cachedCount`: indicates the nodes that are treated as invisible by **Repeat**. These nodes are idle and are temporarily stored in the framework. You can update these nodes as required to implement reuse.
95
96## Use Scenarios
97
98### non-virtualScroll
99
100#### Changing the Data Source
101
102When **Repeat** component implements the non-initial rendering, it compares all keys in the last update with those in the latest update. If the current key is the same as the last one, **Repeat** reuses the child component and updates the **RepeatItem.index** index.
103
104After **Repeat** compares all duplicate keys and reuses them, if the last key is unique and a new key is generated after this update, a child component needs to be created. In this case, **Repeat** will reuse redundant child components and update the **RepeatItem.item** data source and **RepeatItem.index** index.
105
106If the number of remaining child components is greater than or equal to the number of newly updated components, the components are fully reused. If the number of remaining child components is less than the number of newly updated components, **Repeat** will create components corresponding to the extra data items after the remaining components are all reused.
107
108```ts
109@Entry
110@ComponentV2
111struct Parent {
112  @Local simpleList: Array<string> = ['one', 'two', 'three'];
113
114  build() {
115    Row() {
116      Column() {
117        Text('Click to change the value of the third array item')
118          .fontSize(24)
119          .fontColor(Color.Red)
120          .onClick(() => {
121            this.simpleList[2] = 'new three';
122          })
123
124        Repeat<string>(this.simpleList)
125            .each((obj: RepeatItem<string>)=>{
126              ChildItem({ item: obj.item })
127                .margin({top: 20})
128            })
129            .key((item: string) => item)
130      }
131      .justifyContent(FlexAlign.Center)
132      .width('100%')
133      .height('100%')
134    }
135    .height('100%')
136    .backgroundColor(0xF1F3F5)
137  }
138}
139
140@ComponentV2
141struct ChildItem {
142  @Param @Require item: string;
143
144  build() {
145    Text(this.item)
146      .fontSize(30)
147  }
148}
149```
150
151![ForEach-Non-Initial-Render-Case-Effect](./figures/ForEach-Non-Initial-Render-Case-Effect.gif)
152
153The component of the third array item is reused when the array item is re-rendered, and only the data is refreshed.
154
155#### Changing the Index Value
156
157In the following example, when array items 1 and 2 are exchanged, if the key is as the same as the last one, **Repeat** reuses the previous component and updates only the data of the component that uses the **index** value.
158
159```ts
160@Entry
161@ComponentV2
162struct Parent {
163  @Local simpleList: Array<string> = ['one', 'two', 'three'];
164
165  build() {
166    Row() {
167      Column() {
168        Text ('Exchange array items 1 and 2')
169          .fontSize(24)
170          .fontColor(Color.Red)
171          .onClick(() => {
172            let temp: string = this.simpleList[2]
173            this.simpleList[2] = this.simpleList[1]
174            this.simpleList[1] = temp
175          })
176          .margin({bottom: 20})
177
178        Repeat<string>(this.simpleList)
179          .each((obj: RepeatItem<string>)=>{
180            Text("index: " + obj.index)
181              .fontSize(30)
182            ChildItem({ item: obj.item })
183              .margin({bottom: 20})
184          })
185          .key((item: string) => item)
186      }
187      .justifyContent(FlexAlign.Center)
188      .width('100%')
189      .height('100%')
190    }
191    .height('100%')
192    .backgroundColor(0xF1F3F5)
193  }
194}
195
196@ComponentV2
197struct ChildItem {
198  @Param @Require item: string;
199
200  build() {
201    Text(this.item)
202      .fontSize(30)
203  }
204}
205```
206
207![Repeat-Non-Initial-Render-Case-Exchange-Effect](./figures/Repeat-Non-Initial-Render-Case-Exchange-Effect.gif)
208
209### virtualScroll
210
211This section describes the actual application scenarios of **Repeat** and the reuse of component nodes in the **virtualScroll** scenario. A large number of test scenarios can be derived based on reuse rules. This section only describes typical data changes.
212
213#### Examples
214
215The following code designs typical data source operations in the **virtualScroll** scenario of the **Repeat** component, including **inserting, modifying, deleting, and exchanging data**. Click the corresponding text to trigger the data change. Click two data items in sequence to exchange them.
216
217```ts
218@ObservedV2
219class Clazz {
220  @Trace message: string = '';
221
222  constructor(message: string) {
223    this.message = message;
224  }
225}
226
227@Entry
228@ComponentV2
229struct TestPage {
230  @Local simpleList: Array<Clazz> = [];
231  private exchange: number[] = [];
232  private counter: number = 0;
233
234  aboutToAppear(): void {
235    for (let i = 0; i < 100; i++) {
236      this.simpleList.push(new Clazz('Hello ' + i));
237    }
238  }
239
240  build() {
241    Column({ space: 10 }) {
242      Text('Click to insert the fifth item.')
243        .fontSize(24)
244        .fontColor(Color.Red)
245        .onClick(() => {
246          this.simpleList.splice(4, 0, new Clazz(`${this.counter++}_new item`));
247        })
248      Text('Click to modify the fifth item.')
249        .fontSize(24)
250        .fontColor(Color.Red)
251        .onClick(() => {
252          this.simpleList[4].message = `${this.counter++}_new item`;
253        })
254      Text ('Click to delete the fifth item.')
255        .fontSize(24)
256        .fontColor(Color.Red)
257        .onClick(() => {
258          this.simpleList.splice(4, 1);
259        })
260      Text('Click two items to change them.')
261        .fontSize(24)
262        .fontColor(Color.Red)
263
264      List({ initialIndex: 10 }) {
265        Repeat<Clazz>(this.simpleList)
266          .each((obj: RepeatItem<Clazz>) => {
267            ListItem() {
268              Text('[each] ' + obj.item.message)
269                .fontSize(30)
270                .margin({ top: 10 })
271            }
272          })
273          .key((item: Clazz, index: number) => {
274            return item.message;
275          })
276          .virtualScroll({ totalCount: this.simpleList.length })
277          .templateId((item: Clazz, index: number) => "default")
278          .template('default', (ri) => {
279            Text('[template] ' + ri.item.message)
280              .fontSize(30)
281              .margin({ top: 10 })
282              .onClick(() => {
283                this.exchange.push(ri.index);
284                if (this.exchange.length === 2) {
285                  let _a = this.exchange[0];
286                  let _b = this.exchange[1];
287                  // click to exchange
288                  let temp: string = this.simpleList[_a].message;
289                  this.simpleList[_a].message = this.simpleList[_b].message;
290                  this.simpleList[_b].message = temp;
291                  this.exchange = [];
292                }
293              })
294          }, { cachedCount: 3 })
295      }
296      .cachedCount(1)
297      .border({ width: 1 })
298      .width('90%')
299      .height('70%')
300    }
301    .height('100%')
302    .justifyContent(FlexAlign.Center)
303  }
304}
305```
306The following figure lists 100 **message** string properties of the custom class **Clazz**. The **cachedCount** of the **List** component is set to 1, and the size of the **template "default"** cache pool is set to 3. The application screen is shown as bellow.
307
308![Repeat-VirtualScroll-Demo](./figures/Repeat-VirtualScroll-Demo.jpg)
309
310#### Node Operation Instance
311
312When the data source is changed, the node whose key is changed will be re-created. If a cache node exists in the cache pool of the corresponding template, the node is reused. When the **key** remains unchanged, the component reuses and updates the **index** value.
313
314**Inserting Data**
315
316Operations
317
318![Repeat-VirtualScroll-InsertData](./figures/Repeat-VirtualScroll-InsertData.gif)
319
320This example shows four data insertions. Two data items are inserted on the upper part of the screen for the first two times, and another two are inserted on the current screen for the last two times. Print the execution state of the **onUpdateNode** function. "[Old key]->[New key]" indicates that the old node reuses the new node. The node reuse is as follows:
321
322```
323// Insert data twice on the upper part of the screen.
324onUpdateNode [Hello 22] -> [Hello 8]
325onUpdateNode [Hello 21] -> [Hello 7]
326// Insert data twice on the current screen.
327onUpdateNode [Hello 11] -> [2_new item]
328onUpdateNode [Hello 10] -> [3_new item]
329```
330
331When data is inserted on the upper part of the screen, the nodes move. As a result, the pre-loading node of the current screen changes and is reused. That is, the node 22 that exits the cache in the lower part is reused by the node 8 that enters the cache in the upper part. When data is inserted into the current screen, a new data item is generated. The new node will reuse the cached pre-loading node on the lower part of the screen. Data will not be reused when you add data to the lower part of the screen.
332
333**Modifying Data**
334
335Operations
336
337![Repeat-VirtualScroll-ModifyData](./figures/Repeat-VirtualScroll-ModifyData.gif)
338
339This example shows four data modifications. Two data items are modified on the upper part of the screen for the first two times, and another two are modified on the current screen for the last two times. Print the execution state of the **onUpdateNode** function. "[Old key]->[New key]" indicates that the old node reuses the new node. The node reuse is as follows:
340
341```
342// Modify data twice on the current screen.
343onUpdateNode [1_new item] -> [2_new item]
344onUpdateNode [2_new item] -> [3_new item]
345```
346
347Because the rendering nodes does not exist in the upper or lower part of the screen, node reuse does not occur. When a node on the current screen is modified, the template ID of the node does not change. Therefore, the node is reused.
348
349**Exchanging Data**
350
351Operations
352
353![Repeat-VirtualScroll-ExchangeData](./figures/Repeat-VirtualScroll-ExchangeData.gif)
354
355This example shows two data exchanges. Exchanging two nodes does not change the keys, so no node will be reused.
356
357**Deleting Data**
358
359Operations
360
361![Repeat-VirtualScroll-DeleteData](./figures/Repeat-VirtualScroll-DeleteData.gif)
362
363This example shows five data deletions. Two data items are deleted on the upper part of the screen for the first two times, and another three are deleted on the current screen for the last three times. Print the execution state of the **onUpdateNode** function. "[Old key]->[New key]" indicates that the old node reuses the new node. The node reuse is as follows:
364
365```
366// Delete data twice on the upper part of the screen.
367onUpdateNode [Hello 9] -> [Hello 23]
368onUpdateNode [Hello 10] -> [Hello 24]
369// The onUpdateNode function is not called when the data is deleted twice on the current screen.
370// The data on the current screen is deleted for the third time.
371onUpdateNode [Hello 6] -> [Hello 17]
372```
373
374When data is deleted from the upper part of the screen, the nodes move. As a result, the pre-loading node of the current screen changes and is reused. That is, the node 9 that exits the cache in the upper part is reused by the node 23 that enters the cache in the lower part. When data is deleted from the current screen, because of the **cachedCount** pre-loading property of the **List** component, the node that enters the screen in the first two deletions has been rendered and will not be reused. The deleted node enters the cache pool of the corresponding template. In the third deletion, the pre-loading node 17 that enters from the lower part reuses the node 6 in the cache pool.
375
376#### Using Multiple Templates
377
378```
379@ObservedV2
380class Wrap1 {
381    @Trace message: string = '';
382
383    constructor(message: string) {
384        this.message = message;
385    }
386}
387
388@Entry
389@ComponentV2
390struct Parent {
391    @Local simpleList: Array<Wrap1> = [];
392
393    aboutToAppear(): void {
394        for (let i=0; i<100; i++) {
395            this.simpleList.push(new Wrap1('Hello' + i));
396        }
397    }
398
399    build() {
400        Column() {
401            List() {
402                Repeat<Wrap1>(this.simpleList)
403                	.each((obj: RepeatItem<Wrap1>)=>{
404                    	ListItem() {
405                    		Row() {
406                    			Text('default index ' + obj.index + ': ')
407                            		.fontSize(30)
408                            	Text(obj.item.message)
409                            		.fontSize(30)
410                    		}
411                        }
412                        .margin(20)
413                	})
414                	.template('odd', (obj: RepeatItem<Wrap1>)=>{
415                    	ListItem() {
416                    		Row() {
417                    			Text('odd index ' + obj.index + ': ')
418                            		.fontSize(30)
419                            		.fontColor(Color.Blue)
420                            	Text(obj.item.message)
421                            		.fontSize(30)
422                            		.fontColor(Color.Blue)
423                    		}
424                        }
425                        .margin(20)
426                	})
427                	.template('even', (obj: RepeatItem<Wrap1>)=>{
428                    	ListItem() {
429                    		Row() {
430                    			Text('even index ' + obj.index + ': ')
431                            		.fontSize(30)
432                            		.fontColor(Color.Green)
433                            	Text(obj.item.message)
434                            		.fontSize(30)
435                            		.fontColor(Color.Green)
436                    		}
437                        }
438                        .margin(20)
439                	})
440                	.templateId((item: Wrap1, index: number) => {
441                		return index%2 ? 'odd' : 'even';
442                	})
443                	.key((item: Wrap1, index: number) => {
444                		return item.message;
445                	})
446            }
447            .cachedCount(5)
448            .width('100%')
449            .height('100%')
450        }
451        .height('100%')
452    }
453}
454```
455
456![Repeat-VirtualScroll-DataChange](./figures/Repeat-VirtualScroll-Template.gif)
457
458#### The GUI is rendered abnormally when the keys are the same.
459
460If the duplicate keys are misused in the virtualScroll scenario, the GUI rendering is abnormal.
461
462```ts
463@Entry
464@ComponentV2
465struct RepeatKey {
466  @Local simpleList: Array<string> = [];
467
468  aboutToAppear(): void {
469    for (let i = 0; i < 200; i++) {
470      this.simpleList.push(`item ${i}`);
471    }
472  }
473
474  build() {
475    Column({ space: 10 }) {
476      List() {
477        Repeat<string>(this.simpleList)
478          .each((obj: RepeatItem<string>) => {
479            ListItem() {
480              Text(obj.item)
481                .fontSize(30)
482            }
483          })
484          .key((item: string, index: number) => {
485            return 'same key'; // Define the same key.
486          })
487          .virtualScroll({ totalCount: 200 })
488          .templateId((item:string, index: number) => 'default')
489          .template('default', (ri) => {
490            Text(ri.item)
491              .fontSize(30)
492          }, { cachedCount: 2 })
493      }
494      .cachedCount(2)
495      .border({ width: 1 })
496      .width('90%')
497      .height('70%')
498    }
499    .justifyContent(FlexAlign.Center)
500    .width('100%')
501    .height('100%')
502  }
503}
504```
505
506The following figure shows the abnormal effect (the first data item **item 0** disappears).
507
508<img src="./figures/Repeat-VirtualScroll-Same-Key.jpg" width="300" />
509
510## FAQs
511
512### Ensure that the Position of the Scrollbar Remains Unchanged When the List Data Outside the Screen Changes
513
514Declare the **Repeat** component in the **List** component to implement the **key** generation logic and **each** logic (as shown in the following sample code). Click **insert** to insert an element before the first element displayed on the screen, enabling the screen to scroll down.
515
516```ts
517// Define a class and mark it as observable.
518// Customize an array in the class and mark it as traceable.
519@ObservedV2
520class ArrayHolder {
521  @Trace arr: Array<number> = [];
522
523  // constructor, used to initialize arrays.
524  constructor(count: number) {
525    for (let i = 0; i < count; i++) {
526      this.arr.push(i);
527    }
528  }
529}
530
531@Entry
532@ComponentV2
533export struct RepeatTemplateSingle {
534  @Local arrayHolder: ArrayHolder = new ArrayHolder(100);
535  @Local totalCount: number = this.arrayHolder.arr.length;
536  scroller: Scroller = new Scroller();
537
538  build() {
539    Column({ space: 5 }) {
540      List({ space: 20, initialIndex: 19, scroller: this.scroller }) {
541        Repeat(this.arrayHolder.arr)
542          .virtualScroll({ totalCount: this.totalCount })
543          .templateId((item, index) => {
544            return 'number';
545          })
546          .template('number', (r) => {
547            ListItem() {
548              Text(r.index! + ":" + r.item + "Reuse");
549            }
550          })
551          .each((r) => {
552            ListItem() {
553              Text(r.index! + ":" + r.item + "eachMessage");
554            }
555          })
556      }
557      .height('30%')
558
559      Button(`insert totalCount ${this.totalCount}`)
560        .height(60)
561        .onClick(() => {
562          // Insert an element which locates in the previous position displayed on the screen.
563          this.arrayHolder.arr.splice(18, 0, this.totalCount);
564          this.totalCount = this.arrayHolder.arr.length;
565        })
566    }
567    .width('100%')
568    .margin({ top: 5 })
569  }
570}
571```
572
573The figure below shows the effect.
574
575![Repeat-case1-Error](./figures/Repeat-Case1-Error.gif)
576
577In some scenarios, if you do not want the data source change outside the screen to affect the position where the **Scroller** of the **List** stays on the screen, you can use the [onScrollIndex](https://gitee.com/openharmony/docs/blob/master/en/application-dev/ui/arkts-layout-development-create-list.md#responding-to-the-scrolling-position) of the **List** component to listen for the scrolling action. When the list scrolls, you can obtain the scrolling position of a list. Use the [scrollToIndex](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis-arkui/arkui-ts/ts-container-scroll.md#scrolltoindex) feature of the **Scroller** component to slide to the specified **index** position. In this way, when data is added to or deleted from the data source outside the screen, the position where the **Scroller** stays remains unchanged.
578
579The following code shows the case of adding data to the data source.
580
581```ts
582// Define a class and mark it as observable.
583// Customize an array in the class and mark it as traceable.
584@ObservedV2
585class ArrayHolder {
586  @Trace arr: Array<number> = [];
587
588  // constructor, used to initialize arrays.
589  constructor(count: number) {
590    for (let i = 0; i < count; i++) {
591      this.arr.push(i);
592    }
593  }
594}
595
596@Entry
597@ComponentV2
598export struct RepeatTemplateSingle {
599  @Local arrayHolder: ArrayHolder = new ArrayHolder(100);
600  @Local totalCount: number = this.arrayHolder.arr.length;
601  scroller: Scroller = new Scroller();
602
603  private start: number = 1;
604  private end: number = 1;
605
606  build() {
607    Column({ space: 5 }) {
608      List({ space: 20, initialIndex: 19, scroller: this.scroller }) {
609        Repeat(this.arrayHolder.arr)
610          .virtualScroll({ totalCount: this.totalCount })
611          .templateId((item, index) => {
612            return 'number';
613          })
614          .template('number', (r) => {
615            ListItem() {
616              Text(r.index! + ":" + r.item + "Reuse");
617            }
618          })
619          .each((r) => {
620            ListItem() {
621              Text(r.index! + ":" + r.item + "eachMessage");
622            }
623          })
624      }
625      .onScrollIndex((start, end) => {
626        this.start = start;
627        this.end = end;
628      })
629      .height('30%')
630
631      Button(`insert totalCount ${this.totalCount}`)
632        .height(60)
633        .onClick(() => {
634          // Insert an element which locates in the previous position displayed on the screen.
635          this.arrayHolder.arr.splice(18, 0, this.totalCount);
636          let rect = this.scroller.getItemRect(this.start); // Obtain the size and position of the child component.
637          this.scroller.scrollToIndex(this.start + 1); // Slide to the specified index.
638          this.scroller.scrollBy(0, -rect.y); // Slide by a specified distance.
639          this.totalCount = this.arrayHolder.arr.length;
640        })
641    }
642    .width('100%')
643    .margin({ top: 5 })
644  }
645}
646```
647
648The figure below shows the effect.
649
650![Repeat-case1-Succ](./figures/Repeat-Case1-Succ.gif)
651
652### The totalCount Value Is Greater Than the Length of Data Source
653
654When the total length of the data source is large, the lazy loading is used to load some data first. To enable **Repeat** to display the correct scrollbar style, you need to change the value of **totalCount** to the total length of data. That is, before all data sources are loaded, the value of **totalCount** is greater than that of **array.length**.
655
656When the **Repeat** component is initialized, the application must provide sufficient data items for rendering. During the scrolling process of the parent container, the application needs to execute the request instruction for subsequent data items before rendering to ensure that no blank area is displayed during the list sliding process until all data sources are loaded.
657
658You can use the callback of [onScrollIndex](https://gitee.com/openharmony/docs/blob/master/en/application-dev/ui/arkts-layout-development-create-list.md#controlling-the-scrolling-position) attribute of the **List** or **Grid** parent component to implement the preceding specification. The sample code is as follows:
659
660```ts
661@ObservedV2
662class VehicleData {
663  @Trace name: string;
664  @Trace price: number;
665
666  constructor(name: string, price: number) {
667    this.name = name;
668    this.price = price;
669  }
670}
671
672@ObservedV2
673class VehicleDB {
674  public vehicleItems: VehicleData[] = [];
675
676  constructor() {
677    // init data size 20
678    for (let i = 1; i <= 20; i++) {
679      this.vehicleItems.push(new VehicleData(`Vehicle${i}`, i));
680    }
681  }
682}
683
684@Entry
685@ComponentV2
686struct entryCompSucc {
687  @Local vehicleItems: VehicleData[] = new VehicleDB().vehicleItems;
688  @Local listChildrenSize: ChildrenMainSize = new ChildrenMainSize(60);
689  @Local totalCount: number = this.vehicleItems.length;
690  scroller: Scroller = new Scroller();
691
692  build() {
693    Column({ space: 3 }) {
694      List({ scroller: this.scroller }) {
695        Repeat(this.vehicleItems)
696          .virtualScroll({ totalCount: 50 }) // total data size 50
697          .templateId(() => 'default')
698          .template('default', (ri) => {
699            ListItem() {
700              Column() {
701                Text(`${ri.item.name} + ${ri.index}`)
702                  .width('90%')
703                  .height(this.listChildrenSize.childDefaultSize)
704                  .backgroundColor(0xFFA07A)
705                  .textAlign(TextAlign.Center)
706                  .fontSize(20)
707                  .fontWeight(FontWeight.Bold)
708              }
709            }.border({ width: 1 })
710          }, { cachedCount: 5 })
711          .each((ri) => {
712            ListItem() {
713              Text("Wrong: " + `${ri.item.name} + ${ri.index}`)
714                .width('90%')
715                .height(this.listChildrenSize.childDefaultSize)
716                .backgroundColor(0xFFA07A)
717                .textAlign(TextAlign.Center)
718                .fontSize(20)
719                .fontWeight(FontWeight.Bold)
720            }.border({ width: 1 })
721          })
722          .key((item, index) => `${index}:${item}`)
723      }
724      .height('50%')
725      .margin({ top: 20 })
726      .childrenMainSize(this.listChildrenSize)
727      .alignListItem(ListItemAlign.Center)
728      .onScrollIndex((start, end) => {
729        console.log('onScrollIndex', start, end);
730        // lazy data loading
731        if (this.vehicleItems.length < 50) {
732          for (let i = 0; i < 10; i++) {
733            if (this.vehicleItems.length < 50) {
734              this.vehicleItems.push(new VehicleData("Vehicle_loaded", i));
735            }
736          }
737        }
738      })
739    }
740  }
741}
742```
743
744The figure below shows the effect.
745
746![Repeat-Case2-Succ](./figures/Repeat-Case2-Succ.gif)
747
748<!--no_check-->