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 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 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 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 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 73 74#### Data Update Scenarios 75 76 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 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 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 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 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 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 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 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 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 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 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 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 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 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 747 748<!--no_check-->