1e41f4b71Sopenharmony_ci# Repeat: Reusing Child Components 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci>**NOTE** 4e41f4b71Sopenharmony_ci> 5e41f4b71Sopenharmony_ci>Repeat is supported since API version 12. 6e41f4b71Sopenharmony_ci> 7e41f4b71Sopenharmony_ci>State management V2 is still under development, and some features may be incomplete or not always work as expected. 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ciFor 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). 10e41f4b71Sopenharmony_ci 11e41f4b71Sopenharmony_ciWhen **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. 12e41f4b71Sopenharmony_ci 13e41f4b71Sopenharmony_ciWhen **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. 14e41f4b71Sopenharmony_ci 15e41f4b71Sopenharmony_ci> **Note:** 16e41f4b71Sopenharmony_ci> 17e41f4b71Sopenharmony_ci> 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. 18e41f4b71Sopenharmony_ci 19e41f4b71Sopenharmony_ci## Constraints 20e41f4b71Sopenharmony_ci 21e41f4b71Sopenharmony_ci- **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. 22e41f4b71Sopenharmony_ci- After **virtualScroll** is enabled for **Repeat**, only one child component can be created in each iteration. Otherwise, there is no constraint. 23e41f4b71Sopenharmony_ci- The generated child components must be allowed in the parent container component of **Repeat**. 24e41f4b71Sopenharmony_ci- **Repeat** can be included in an **if/else** statement, and can also contain such a statement. 25e41f4b71Sopenharmony_ci- **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. 26e41f4b71Sopenharmony_ci- If **virtualScroll** is disabled, the **template** is not supported currently and problems may occur when reusing. 27e41f4b71Sopenharmony_ci- 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. 28e41f4b71Sopenharmony_ci- 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. 29e41f4b71Sopenharmony_ci 30e41f4b71Sopenharmony_ci## Key Generation Rules 31e41f4b71Sopenharmony_ci 32e41f4b71Sopenharmony_ci### non-virtualScroll 33e41f4b71Sopenharmony_ci 34e41f4b71Sopenharmony_ci 35e41f4b71Sopenharmony_ci 36e41f4b71Sopenharmony_ci### virtualScroll 37e41f4b71Sopenharmony_ci 38e41f4b71Sopenharmony_ci**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. 39e41f4b71Sopenharmony_ci 40e41f4b71Sopenharmony_ci 41e41f4b71Sopenharmony_ci 42e41f4b71Sopenharmony_ci## Component Generation and Reuse Rules 43e41f4b71Sopenharmony_ci 44e41f4b71Sopenharmony_ci### non-virtualScroll 45e41f4b71Sopenharmony_ci 46e41f4b71Sopenharmony_ciAll child components are created when **Repeat** is rendered for the first time. The original components are reused when data is updated. 47e41f4b71Sopenharmony_ci 48e41f4b71Sopenharmony_ciWhen 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. 49e41f4b71Sopenharmony_ci 50e41f4b71Sopenharmony_ciAfter **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. 51e41f4b71Sopenharmony_ci 52e41f4b71Sopenharmony_ciIf 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. 53e41f4b71Sopenharmony_ci 54e41f4b71Sopenharmony_ci### virtualScroll 55e41f4b71Sopenharmony_ci 56e41f4b71Sopenharmony_ciAt 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. 57e41f4b71Sopenharmony_ci 58e41f4b71Sopenharmony_ci#### Slide shortcut 59e41f4b71Sopenharmony_ci 60e41f4b71Sopenharmony_ciThe following figure describes the node state before sliding. 61e41f4b71Sopenharmony_ci 62e41f4b71Sopenharmony_ci 63e41f4b71Sopenharmony_ci 64e41f4b71Sopenharmony_ciCurrently, 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. 65e41f4b71Sopenharmony_ci 66e41f4b71Sopenharmony_ci 67e41f4b71Sopenharmony_ci 68e41f4b71Sopenharmony_ciThe 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. 69e41f4b71Sopenharmony_ci 70e41f4b71Sopenharmony_ciThe **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. 71e41f4b71Sopenharmony_ci 72e41f4b71Sopenharmony_ci 73e41f4b71Sopenharmony_ci 74e41f4b71Sopenharmony_ci#### Data Update Scenarios 75e41f4b71Sopenharmony_ci 76e41f4b71Sopenharmony_ci 77e41f4b71Sopenharmony_ci 78e41f4b71Sopenharmony_ciIn 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. 79e41f4b71Sopenharmony_ci 80e41f4b71Sopenharmony_ci 81e41f4b71Sopenharmony_ci 82e41f4b71Sopenharmony_ciNow, **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**. 83e41f4b71Sopenharmony_ci 84e41f4b71Sopenharmony_ci 85e41f4b71Sopenharmony_ci 86e41f4b71Sopenharmony_ciAs 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. 87e41f4b71Sopenharmony_ci 88e41f4b71Sopenharmony_ci 89e41f4b71Sopenharmony_ci 90e41f4b71Sopenharmony_ci## cachedCount Rules 91e41f4b71Sopenharmony_ci 92e41f4b71Sopenharmony_ciThe 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. 93e41f4b71Sopenharmony_ci- **.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. 94e41f4b71Sopenharmony_ci- 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. 95e41f4b71Sopenharmony_ci 96e41f4b71Sopenharmony_ci## Use Scenarios 97e41f4b71Sopenharmony_ci 98e41f4b71Sopenharmony_ci### non-virtualScroll 99e41f4b71Sopenharmony_ci 100e41f4b71Sopenharmony_ci#### Changing the Data Source 101e41f4b71Sopenharmony_ci 102e41f4b71Sopenharmony_ciWhen **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. 103e41f4b71Sopenharmony_ci 104e41f4b71Sopenharmony_ciAfter **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. 105e41f4b71Sopenharmony_ci 106e41f4b71Sopenharmony_ciIf 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. 107e41f4b71Sopenharmony_ci 108e41f4b71Sopenharmony_ci```ts 109e41f4b71Sopenharmony_ci@Entry 110e41f4b71Sopenharmony_ci@ComponentV2 111e41f4b71Sopenharmony_cistruct Parent { 112e41f4b71Sopenharmony_ci @Local simpleList: Array<string> = ['one', 'two', 'three']; 113e41f4b71Sopenharmony_ci 114e41f4b71Sopenharmony_ci build() { 115e41f4b71Sopenharmony_ci Row() { 116e41f4b71Sopenharmony_ci Column() { 117e41f4b71Sopenharmony_ci Text('Click to change the value of the third array item') 118e41f4b71Sopenharmony_ci .fontSize(24) 119e41f4b71Sopenharmony_ci .fontColor(Color.Red) 120e41f4b71Sopenharmony_ci .onClick(() => { 121e41f4b71Sopenharmony_ci this.simpleList[2] = 'new three'; 122e41f4b71Sopenharmony_ci }) 123e41f4b71Sopenharmony_ci 124e41f4b71Sopenharmony_ci Repeat<string>(this.simpleList) 125e41f4b71Sopenharmony_ci .each((obj: RepeatItem<string>)=>{ 126e41f4b71Sopenharmony_ci ChildItem({ item: obj.item }) 127e41f4b71Sopenharmony_ci .margin({top: 20}) 128e41f4b71Sopenharmony_ci }) 129e41f4b71Sopenharmony_ci .key((item: string) => item) 130e41f4b71Sopenharmony_ci } 131e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 132e41f4b71Sopenharmony_ci .width('100%') 133e41f4b71Sopenharmony_ci .height('100%') 134e41f4b71Sopenharmony_ci } 135e41f4b71Sopenharmony_ci .height('100%') 136e41f4b71Sopenharmony_ci .backgroundColor(0xF1F3F5) 137e41f4b71Sopenharmony_ci } 138e41f4b71Sopenharmony_ci} 139e41f4b71Sopenharmony_ci 140e41f4b71Sopenharmony_ci@ComponentV2 141e41f4b71Sopenharmony_cistruct ChildItem { 142e41f4b71Sopenharmony_ci @Param @Require item: string; 143e41f4b71Sopenharmony_ci 144e41f4b71Sopenharmony_ci build() { 145e41f4b71Sopenharmony_ci Text(this.item) 146e41f4b71Sopenharmony_ci .fontSize(30) 147e41f4b71Sopenharmony_ci } 148e41f4b71Sopenharmony_ci} 149e41f4b71Sopenharmony_ci``` 150e41f4b71Sopenharmony_ci 151e41f4b71Sopenharmony_ci 152e41f4b71Sopenharmony_ci 153e41f4b71Sopenharmony_ciThe component of the third array item is reused when the array item is re-rendered, and only the data is refreshed. 154e41f4b71Sopenharmony_ci 155e41f4b71Sopenharmony_ci#### Changing the Index Value 156e41f4b71Sopenharmony_ci 157e41f4b71Sopenharmony_ciIn 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. 158e41f4b71Sopenharmony_ci 159e41f4b71Sopenharmony_ci```ts 160e41f4b71Sopenharmony_ci@Entry 161e41f4b71Sopenharmony_ci@ComponentV2 162e41f4b71Sopenharmony_cistruct Parent { 163e41f4b71Sopenharmony_ci @Local simpleList: Array<string> = ['one', 'two', 'three']; 164e41f4b71Sopenharmony_ci 165e41f4b71Sopenharmony_ci build() { 166e41f4b71Sopenharmony_ci Row() { 167e41f4b71Sopenharmony_ci Column() { 168e41f4b71Sopenharmony_ci Text ('Exchange array items 1 and 2') 169e41f4b71Sopenharmony_ci .fontSize(24) 170e41f4b71Sopenharmony_ci .fontColor(Color.Red) 171e41f4b71Sopenharmony_ci .onClick(() => { 172e41f4b71Sopenharmony_ci let temp: string = this.simpleList[2] 173e41f4b71Sopenharmony_ci this.simpleList[2] = this.simpleList[1] 174e41f4b71Sopenharmony_ci this.simpleList[1] = temp 175e41f4b71Sopenharmony_ci }) 176e41f4b71Sopenharmony_ci .margin({bottom: 20}) 177e41f4b71Sopenharmony_ci 178e41f4b71Sopenharmony_ci Repeat<string>(this.simpleList) 179e41f4b71Sopenharmony_ci .each((obj: RepeatItem<string>)=>{ 180e41f4b71Sopenharmony_ci Text("index: " + obj.index) 181e41f4b71Sopenharmony_ci .fontSize(30) 182e41f4b71Sopenharmony_ci ChildItem({ item: obj.item }) 183e41f4b71Sopenharmony_ci .margin({bottom: 20}) 184e41f4b71Sopenharmony_ci }) 185e41f4b71Sopenharmony_ci .key((item: string) => item) 186e41f4b71Sopenharmony_ci } 187e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 188e41f4b71Sopenharmony_ci .width('100%') 189e41f4b71Sopenharmony_ci .height('100%') 190e41f4b71Sopenharmony_ci } 191e41f4b71Sopenharmony_ci .height('100%') 192e41f4b71Sopenharmony_ci .backgroundColor(0xF1F3F5) 193e41f4b71Sopenharmony_ci } 194e41f4b71Sopenharmony_ci} 195e41f4b71Sopenharmony_ci 196e41f4b71Sopenharmony_ci@ComponentV2 197e41f4b71Sopenharmony_cistruct ChildItem { 198e41f4b71Sopenharmony_ci @Param @Require item: string; 199e41f4b71Sopenharmony_ci 200e41f4b71Sopenharmony_ci build() { 201e41f4b71Sopenharmony_ci Text(this.item) 202e41f4b71Sopenharmony_ci .fontSize(30) 203e41f4b71Sopenharmony_ci } 204e41f4b71Sopenharmony_ci} 205e41f4b71Sopenharmony_ci``` 206e41f4b71Sopenharmony_ci 207e41f4b71Sopenharmony_ci 208e41f4b71Sopenharmony_ci 209e41f4b71Sopenharmony_ci### virtualScroll 210e41f4b71Sopenharmony_ci 211e41f4b71Sopenharmony_ciThis 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. 212e41f4b71Sopenharmony_ci 213e41f4b71Sopenharmony_ci#### Examples 214e41f4b71Sopenharmony_ci 215e41f4b71Sopenharmony_ciThe 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. 216e41f4b71Sopenharmony_ci 217e41f4b71Sopenharmony_ci```ts 218e41f4b71Sopenharmony_ci@ObservedV2 219e41f4b71Sopenharmony_ciclass Clazz { 220e41f4b71Sopenharmony_ci @Trace message: string = ''; 221e41f4b71Sopenharmony_ci 222e41f4b71Sopenharmony_ci constructor(message: string) { 223e41f4b71Sopenharmony_ci this.message = message; 224e41f4b71Sopenharmony_ci } 225e41f4b71Sopenharmony_ci} 226e41f4b71Sopenharmony_ci 227e41f4b71Sopenharmony_ci@Entry 228e41f4b71Sopenharmony_ci@ComponentV2 229e41f4b71Sopenharmony_cistruct TestPage { 230e41f4b71Sopenharmony_ci @Local simpleList: Array<Clazz> = []; 231e41f4b71Sopenharmony_ci private exchange: number[] = []; 232e41f4b71Sopenharmony_ci private counter: number = 0; 233e41f4b71Sopenharmony_ci 234e41f4b71Sopenharmony_ci aboutToAppear(): void { 235e41f4b71Sopenharmony_ci for (let i = 0; i < 100; i++) { 236e41f4b71Sopenharmony_ci this.simpleList.push(new Clazz('Hello ' + i)); 237e41f4b71Sopenharmony_ci } 238e41f4b71Sopenharmony_ci } 239e41f4b71Sopenharmony_ci 240e41f4b71Sopenharmony_ci build() { 241e41f4b71Sopenharmony_ci Column({ space: 10 }) { 242e41f4b71Sopenharmony_ci Text('Click to insert the fifth item.') 243e41f4b71Sopenharmony_ci .fontSize(24) 244e41f4b71Sopenharmony_ci .fontColor(Color.Red) 245e41f4b71Sopenharmony_ci .onClick(() => { 246e41f4b71Sopenharmony_ci this.simpleList.splice(4, 0, new Clazz(`${this.counter++}_new item`)); 247e41f4b71Sopenharmony_ci }) 248e41f4b71Sopenharmony_ci Text('Click to modify the fifth item.') 249e41f4b71Sopenharmony_ci .fontSize(24) 250e41f4b71Sopenharmony_ci .fontColor(Color.Red) 251e41f4b71Sopenharmony_ci .onClick(() => { 252e41f4b71Sopenharmony_ci this.simpleList[4].message = `${this.counter++}_new item`; 253e41f4b71Sopenharmony_ci }) 254e41f4b71Sopenharmony_ci Text ('Click to delete the fifth item.') 255e41f4b71Sopenharmony_ci .fontSize(24) 256e41f4b71Sopenharmony_ci .fontColor(Color.Red) 257e41f4b71Sopenharmony_ci .onClick(() => { 258e41f4b71Sopenharmony_ci this.simpleList.splice(4, 1); 259e41f4b71Sopenharmony_ci }) 260e41f4b71Sopenharmony_ci Text('Click two items to change them.') 261e41f4b71Sopenharmony_ci .fontSize(24) 262e41f4b71Sopenharmony_ci .fontColor(Color.Red) 263e41f4b71Sopenharmony_ci 264e41f4b71Sopenharmony_ci List({ initialIndex: 10 }) { 265e41f4b71Sopenharmony_ci Repeat<Clazz>(this.simpleList) 266e41f4b71Sopenharmony_ci .each((obj: RepeatItem<Clazz>) => { 267e41f4b71Sopenharmony_ci ListItem() { 268e41f4b71Sopenharmony_ci Text('[each] ' + obj.item.message) 269e41f4b71Sopenharmony_ci .fontSize(30) 270e41f4b71Sopenharmony_ci .margin({ top: 10 }) 271e41f4b71Sopenharmony_ci } 272e41f4b71Sopenharmony_ci }) 273e41f4b71Sopenharmony_ci .key((item: Clazz, index: number) => { 274e41f4b71Sopenharmony_ci return item.message; 275e41f4b71Sopenharmony_ci }) 276e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: this.simpleList.length }) 277e41f4b71Sopenharmony_ci .templateId((item: Clazz, index: number) => "default") 278e41f4b71Sopenharmony_ci .template('default', (ri) => { 279e41f4b71Sopenharmony_ci Text('[template] ' + ri.item.message) 280e41f4b71Sopenharmony_ci .fontSize(30) 281e41f4b71Sopenharmony_ci .margin({ top: 10 }) 282e41f4b71Sopenharmony_ci .onClick(() => { 283e41f4b71Sopenharmony_ci this.exchange.push(ri.index); 284e41f4b71Sopenharmony_ci if (this.exchange.length === 2) { 285e41f4b71Sopenharmony_ci let _a = this.exchange[0]; 286e41f4b71Sopenharmony_ci let _b = this.exchange[1]; 287e41f4b71Sopenharmony_ci // click to exchange 288e41f4b71Sopenharmony_ci let temp: string = this.simpleList[_a].message; 289e41f4b71Sopenharmony_ci this.simpleList[_a].message = this.simpleList[_b].message; 290e41f4b71Sopenharmony_ci this.simpleList[_b].message = temp; 291e41f4b71Sopenharmony_ci this.exchange = []; 292e41f4b71Sopenharmony_ci } 293e41f4b71Sopenharmony_ci }) 294e41f4b71Sopenharmony_ci }, { cachedCount: 3 }) 295e41f4b71Sopenharmony_ci } 296e41f4b71Sopenharmony_ci .cachedCount(1) 297e41f4b71Sopenharmony_ci .border({ width: 1 }) 298e41f4b71Sopenharmony_ci .width('90%') 299e41f4b71Sopenharmony_ci .height('70%') 300e41f4b71Sopenharmony_ci } 301e41f4b71Sopenharmony_ci .height('100%') 302e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 303e41f4b71Sopenharmony_ci } 304e41f4b71Sopenharmony_ci} 305e41f4b71Sopenharmony_ci``` 306e41f4b71Sopenharmony_ciThe 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. 307e41f4b71Sopenharmony_ci 308e41f4b71Sopenharmony_ci 309e41f4b71Sopenharmony_ci 310e41f4b71Sopenharmony_ci#### Node Operation Instance 311e41f4b71Sopenharmony_ci 312e41f4b71Sopenharmony_ciWhen 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. 313e41f4b71Sopenharmony_ci 314e41f4b71Sopenharmony_ci**Inserting Data** 315e41f4b71Sopenharmony_ci 316e41f4b71Sopenharmony_ciOperations 317e41f4b71Sopenharmony_ci 318e41f4b71Sopenharmony_ci 319e41f4b71Sopenharmony_ci 320e41f4b71Sopenharmony_ciThis 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: 321e41f4b71Sopenharmony_ci 322e41f4b71Sopenharmony_ci``` 323e41f4b71Sopenharmony_ci// Insert data twice on the upper part of the screen. 324e41f4b71Sopenharmony_cionUpdateNode [Hello 22] -> [Hello 8] 325e41f4b71Sopenharmony_cionUpdateNode [Hello 21] -> [Hello 7] 326e41f4b71Sopenharmony_ci// Insert data twice on the current screen. 327e41f4b71Sopenharmony_cionUpdateNode [Hello 11] -> [2_new item] 328e41f4b71Sopenharmony_cionUpdateNode [Hello 10] -> [3_new item] 329e41f4b71Sopenharmony_ci``` 330e41f4b71Sopenharmony_ci 331e41f4b71Sopenharmony_ciWhen 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. 332e41f4b71Sopenharmony_ci 333e41f4b71Sopenharmony_ci**Modifying Data** 334e41f4b71Sopenharmony_ci 335e41f4b71Sopenharmony_ciOperations 336e41f4b71Sopenharmony_ci 337e41f4b71Sopenharmony_ci 338e41f4b71Sopenharmony_ci 339e41f4b71Sopenharmony_ciThis 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: 340e41f4b71Sopenharmony_ci 341e41f4b71Sopenharmony_ci``` 342e41f4b71Sopenharmony_ci// Modify data twice on the current screen. 343e41f4b71Sopenharmony_cionUpdateNode [1_new item] -> [2_new item] 344e41f4b71Sopenharmony_cionUpdateNode [2_new item] -> [3_new item] 345e41f4b71Sopenharmony_ci``` 346e41f4b71Sopenharmony_ci 347e41f4b71Sopenharmony_ciBecause 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. 348e41f4b71Sopenharmony_ci 349e41f4b71Sopenharmony_ci**Exchanging Data** 350e41f4b71Sopenharmony_ci 351e41f4b71Sopenharmony_ciOperations 352e41f4b71Sopenharmony_ci 353e41f4b71Sopenharmony_ci 354e41f4b71Sopenharmony_ci 355e41f4b71Sopenharmony_ciThis example shows two data exchanges. Exchanging two nodes does not change the keys, so no node will be reused. 356e41f4b71Sopenharmony_ci 357e41f4b71Sopenharmony_ci**Deleting Data** 358e41f4b71Sopenharmony_ci 359e41f4b71Sopenharmony_ciOperations 360e41f4b71Sopenharmony_ci 361e41f4b71Sopenharmony_ci 362e41f4b71Sopenharmony_ci 363e41f4b71Sopenharmony_ciThis 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: 364e41f4b71Sopenharmony_ci 365e41f4b71Sopenharmony_ci``` 366e41f4b71Sopenharmony_ci// Delete data twice on the upper part of the screen. 367e41f4b71Sopenharmony_cionUpdateNode [Hello 9] -> [Hello 23] 368e41f4b71Sopenharmony_cionUpdateNode [Hello 10] -> [Hello 24] 369e41f4b71Sopenharmony_ci// The onUpdateNode function is not called when the data is deleted twice on the current screen. 370e41f4b71Sopenharmony_ci// The data on the current screen is deleted for the third time. 371e41f4b71Sopenharmony_cionUpdateNode [Hello 6] -> [Hello 17] 372e41f4b71Sopenharmony_ci``` 373e41f4b71Sopenharmony_ci 374e41f4b71Sopenharmony_ciWhen 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. 375e41f4b71Sopenharmony_ci 376e41f4b71Sopenharmony_ci#### Using Multiple Templates 377e41f4b71Sopenharmony_ci 378e41f4b71Sopenharmony_ci``` 379e41f4b71Sopenharmony_ci@ObservedV2 380e41f4b71Sopenharmony_ciclass Wrap1 { 381e41f4b71Sopenharmony_ci @Trace message: string = ''; 382e41f4b71Sopenharmony_ci 383e41f4b71Sopenharmony_ci constructor(message: string) { 384e41f4b71Sopenharmony_ci this.message = message; 385e41f4b71Sopenharmony_ci } 386e41f4b71Sopenharmony_ci} 387e41f4b71Sopenharmony_ci 388e41f4b71Sopenharmony_ci@Entry 389e41f4b71Sopenharmony_ci@ComponentV2 390e41f4b71Sopenharmony_cistruct Parent { 391e41f4b71Sopenharmony_ci @Local simpleList: Array<Wrap1> = []; 392e41f4b71Sopenharmony_ci 393e41f4b71Sopenharmony_ci aboutToAppear(): void { 394e41f4b71Sopenharmony_ci for (let i=0; i<100; i++) { 395e41f4b71Sopenharmony_ci this.simpleList.push(new Wrap1('Hello' + i)); 396e41f4b71Sopenharmony_ci } 397e41f4b71Sopenharmony_ci } 398e41f4b71Sopenharmony_ci 399e41f4b71Sopenharmony_ci build() { 400e41f4b71Sopenharmony_ci Column() { 401e41f4b71Sopenharmony_ci List() { 402e41f4b71Sopenharmony_ci Repeat<Wrap1>(this.simpleList) 403e41f4b71Sopenharmony_ci .each((obj: RepeatItem<Wrap1>)=>{ 404e41f4b71Sopenharmony_ci ListItem() { 405e41f4b71Sopenharmony_ci Row() { 406e41f4b71Sopenharmony_ci Text('default index ' + obj.index + ': ') 407e41f4b71Sopenharmony_ci .fontSize(30) 408e41f4b71Sopenharmony_ci Text(obj.item.message) 409e41f4b71Sopenharmony_ci .fontSize(30) 410e41f4b71Sopenharmony_ci } 411e41f4b71Sopenharmony_ci } 412e41f4b71Sopenharmony_ci .margin(20) 413e41f4b71Sopenharmony_ci }) 414e41f4b71Sopenharmony_ci .template('odd', (obj: RepeatItem<Wrap1>)=>{ 415e41f4b71Sopenharmony_ci ListItem() { 416e41f4b71Sopenharmony_ci Row() { 417e41f4b71Sopenharmony_ci Text('odd index ' + obj.index + ': ') 418e41f4b71Sopenharmony_ci .fontSize(30) 419e41f4b71Sopenharmony_ci .fontColor(Color.Blue) 420e41f4b71Sopenharmony_ci Text(obj.item.message) 421e41f4b71Sopenharmony_ci .fontSize(30) 422e41f4b71Sopenharmony_ci .fontColor(Color.Blue) 423e41f4b71Sopenharmony_ci } 424e41f4b71Sopenharmony_ci } 425e41f4b71Sopenharmony_ci .margin(20) 426e41f4b71Sopenharmony_ci }) 427e41f4b71Sopenharmony_ci .template('even', (obj: RepeatItem<Wrap1>)=>{ 428e41f4b71Sopenharmony_ci ListItem() { 429e41f4b71Sopenharmony_ci Row() { 430e41f4b71Sopenharmony_ci Text('even index ' + obj.index + ': ') 431e41f4b71Sopenharmony_ci .fontSize(30) 432e41f4b71Sopenharmony_ci .fontColor(Color.Green) 433e41f4b71Sopenharmony_ci Text(obj.item.message) 434e41f4b71Sopenharmony_ci .fontSize(30) 435e41f4b71Sopenharmony_ci .fontColor(Color.Green) 436e41f4b71Sopenharmony_ci } 437e41f4b71Sopenharmony_ci } 438e41f4b71Sopenharmony_ci .margin(20) 439e41f4b71Sopenharmony_ci }) 440e41f4b71Sopenharmony_ci .templateId((item: Wrap1, index: number) => { 441e41f4b71Sopenharmony_ci return index%2 ? 'odd' : 'even'; 442e41f4b71Sopenharmony_ci }) 443e41f4b71Sopenharmony_ci .key((item: Wrap1, index: number) => { 444e41f4b71Sopenharmony_ci return item.message; 445e41f4b71Sopenharmony_ci }) 446e41f4b71Sopenharmony_ci } 447e41f4b71Sopenharmony_ci .cachedCount(5) 448e41f4b71Sopenharmony_ci .width('100%') 449e41f4b71Sopenharmony_ci .height('100%') 450e41f4b71Sopenharmony_ci } 451e41f4b71Sopenharmony_ci .height('100%') 452e41f4b71Sopenharmony_ci } 453e41f4b71Sopenharmony_ci} 454e41f4b71Sopenharmony_ci``` 455e41f4b71Sopenharmony_ci 456e41f4b71Sopenharmony_ci 457e41f4b71Sopenharmony_ci 458e41f4b71Sopenharmony_ci#### The GUI is rendered abnormally when the keys are the same. 459e41f4b71Sopenharmony_ci 460e41f4b71Sopenharmony_ciIf the duplicate keys are misused in the virtualScroll scenario, the GUI rendering is abnormal. 461e41f4b71Sopenharmony_ci 462e41f4b71Sopenharmony_ci```ts 463e41f4b71Sopenharmony_ci@Entry 464e41f4b71Sopenharmony_ci@ComponentV2 465e41f4b71Sopenharmony_cistruct RepeatKey { 466e41f4b71Sopenharmony_ci @Local simpleList: Array<string> = []; 467e41f4b71Sopenharmony_ci 468e41f4b71Sopenharmony_ci aboutToAppear(): void { 469e41f4b71Sopenharmony_ci for (let i = 0; i < 200; i++) { 470e41f4b71Sopenharmony_ci this.simpleList.push(`item ${i}`); 471e41f4b71Sopenharmony_ci } 472e41f4b71Sopenharmony_ci } 473e41f4b71Sopenharmony_ci 474e41f4b71Sopenharmony_ci build() { 475e41f4b71Sopenharmony_ci Column({ space: 10 }) { 476e41f4b71Sopenharmony_ci List() { 477e41f4b71Sopenharmony_ci Repeat<string>(this.simpleList) 478e41f4b71Sopenharmony_ci .each((obj: RepeatItem<string>) => { 479e41f4b71Sopenharmony_ci ListItem() { 480e41f4b71Sopenharmony_ci Text(obj.item) 481e41f4b71Sopenharmony_ci .fontSize(30) 482e41f4b71Sopenharmony_ci } 483e41f4b71Sopenharmony_ci }) 484e41f4b71Sopenharmony_ci .key((item: string, index: number) => { 485e41f4b71Sopenharmony_ci return 'same key'; // Define the same key. 486e41f4b71Sopenharmony_ci }) 487e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: 200 }) 488e41f4b71Sopenharmony_ci .templateId((item:string, index: number) => 'default') 489e41f4b71Sopenharmony_ci .template('default', (ri) => { 490e41f4b71Sopenharmony_ci Text(ri.item) 491e41f4b71Sopenharmony_ci .fontSize(30) 492e41f4b71Sopenharmony_ci }, { cachedCount: 2 }) 493e41f4b71Sopenharmony_ci } 494e41f4b71Sopenharmony_ci .cachedCount(2) 495e41f4b71Sopenharmony_ci .border({ width: 1 }) 496e41f4b71Sopenharmony_ci .width('90%') 497e41f4b71Sopenharmony_ci .height('70%') 498e41f4b71Sopenharmony_ci } 499e41f4b71Sopenharmony_ci .justifyContent(FlexAlign.Center) 500e41f4b71Sopenharmony_ci .width('100%') 501e41f4b71Sopenharmony_ci .height('100%') 502e41f4b71Sopenharmony_ci } 503e41f4b71Sopenharmony_ci} 504e41f4b71Sopenharmony_ci``` 505e41f4b71Sopenharmony_ci 506e41f4b71Sopenharmony_ciThe following figure shows the abnormal effect (the first data item **item 0** disappears). 507e41f4b71Sopenharmony_ci 508e41f4b71Sopenharmony_ci<img src="./figures/Repeat-VirtualScroll-Same-Key.jpg" width="300" /> 509e41f4b71Sopenharmony_ci 510e41f4b71Sopenharmony_ci## FAQs 511e41f4b71Sopenharmony_ci 512e41f4b71Sopenharmony_ci### Ensure that the Position of the Scrollbar Remains Unchanged When the List Data Outside the Screen Changes 513e41f4b71Sopenharmony_ci 514e41f4b71Sopenharmony_ciDeclare 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. 515e41f4b71Sopenharmony_ci 516e41f4b71Sopenharmony_ci```ts 517e41f4b71Sopenharmony_ci// Define a class and mark it as observable. 518e41f4b71Sopenharmony_ci// Customize an array in the class and mark it as traceable. 519e41f4b71Sopenharmony_ci@ObservedV2 520e41f4b71Sopenharmony_ciclass ArrayHolder { 521e41f4b71Sopenharmony_ci @Trace arr: Array<number> = []; 522e41f4b71Sopenharmony_ci 523e41f4b71Sopenharmony_ci // constructor, used to initialize arrays. 524e41f4b71Sopenharmony_ci constructor(count: number) { 525e41f4b71Sopenharmony_ci for (let i = 0; i < count; i++) { 526e41f4b71Sopenharmony_ci this.arr.push(i); 527e41f4b71Sopenharmony_ci } 528e41f4b71Sopenharmony_ci } 529e41f4b71Sopenharmony_ci} 530e41f4b71Sopenharmony_ci 531e41f4b71Sopenharmony_ci@Entry 532e41f4b71Sopenharmony_ci@ComponentV2 533e41f4b71Sopenharmony_ciexport struct RepeatTemplateSingle { 534e41f4b71Sopenharmony_ci @Local arrayHolder: ArrayHolder = new ArrayHolder(100); 535e41f4b71Sopenharmony_ci @Local totalCount: number = this.arrayHolder.arr.length; 536e41f4b71Sopenharmony_ci scroller: Scroller = new Scroller(); 537e41f4b71Sopenharmony_ci 538e41f4b71Sopenharmony_ci build() { 539e41f4b71Sopenharmony_ci Column({ space: 5 }) { 540e41f4b71Sopenharmony_ci List({ space: 20, initialIndex: 19, scroller: this.scroller }) { 541e41f4b71Sopenharmony_ci Repeat(this.arrayHolder.arr) 542e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: this.totalCount }) 543e41f4b71Sopenharmony_ci .templateId((item, index) => { 544e41f4b71Sopenharmony_ci return 'number'; 545e41f4b71Sopenharmony_ci }) 546e41f4b71Sopenharmony_ci .template('number', (r) => { 547e41f4b71Sopenharmony_ci ListItem() { 548e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "Reuse"); 549e41f4b71Sopenharmony_ci } 550e41f4b71Sopenharmony_ci }) 551e41f4b71Sopenharmony_ci .each((r) => { 552e41f4b71Sopenharmony_ci ListItem() { 553e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "eachMessage"); 554e41f4b71Sopenharmony_ci } 555e41f4b71Sopenharmony_ci }) 556e41f4b71Sopenharmony_ci } 557e41f4b71Sopenharmony_ci .height('30%') 558e41f4b71Sopenharmony_ci 559e41f4b71Sopenharmony_ci Button(`insert totalCount ${this.totalCount}`) 560e41f4b71Sopenharmony_ci .height(60) 561e41f4b71Sopenharmony_ci .onClick(() => { 562e41f4b71Sopenharmony_ci // Insert an element which locates in the previous position displayed on the screen. 563e41f4b71Sopenharmony_ci this.arrayHolder.arr.splice(18, 0, this.totalCount); 564e41f4b71Sopenharmony_ci this.totalCount = this.arrayHolder.arr.length; 565e41f4b71Sopenharmony_ci }) 566e41f4b71Sopenharmony_ci } 567e41f4b71Sopenharmony_ci .width('100%') 568e41f4b71Sopenharmony_ci .margin({ top: 5 }) 569e41f4b71Sopenharmony_ci } 570e41f4b71Sopenharmony_ci} 571e41f4b71Sopenharmony_ci``` 572e41f4b71Sopenharmony_ci 573e41f4b71Sopenharmony_ciThe figure below shows the effect. 574e41f4b71Sopenharmony_ci 575e41f4b71Sopenharmony_ci 576e41f4b71Sopenharmony_ci 577e41f4b71Sopenharmony_ciIn 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. 578e41f4b71Sopenharmony_ci 579e41f4b71Sopenharmony_ciThe following code shows the case of adding data to the data source. 580e41f4b71Sopenharmony_ci 581e41f4b71Sopenharmony_ci```ts 582e41f4b71Sopenharmony_ci// Define a class and mark it as observable. 583e41f4b71Sopenharmony_ci// Customize an array in the class and mark it as traceable. 584e41f4b71Sopenharmony_ci@ObservedV2 585e41f4b71Sopenharmony_ciclass ArrayHolder { 586e41f4b71Sopenharmony_ci @Trace arr: Array<number> = []; 587e41f4b71Sopenharmony_ci 588e41f4b71Sopenharmony_ci // constructor, used to initialize arrays. 589e41f4b71Sopenharmony_ci constructor(count: number) { 590e41f4b71Sopenharmony_ci for (let i = 0; i < count; i++) { 591e41f4b71Sopenharmony_ci this.arr.push(i); 592e41f4b71Sopenharmony_ci } 593e41f4b71Sopenharmony_ci } 594e41f4b71Sopenharmony_ci} 595e41f4b71Sopenharmony_ci 596e41f4b71Sopenharmony_ci@Entry 597e41f4b71Sopenharmony_ci@ComponentV2 598e41f4b71Sopenharmony_ciexport struct RepeatTemplateSingle { 599e41f4b71Sopenharmony_ci @Local arrayHolder: ArrayHolder = new ArrayHolder(100); 600e41f4b71Sopenharmony_ci @Local totalCount: number = this.arrayHolder.arr.length; 601e41f4b71Sopenharmony_ci scroller: Scroller = new Scroller(); 602e41f4b71Sopenharmony_ci 603e41f4b71Sopenharmony_ci private start: number = 1; 604e41f4b71Sopenharmony_ci private end: number = 1; 605e41f4b71Sopenharmony_ci 606e41f4b71Sopenharmony_ci build() { 607e41f4b71Sopenharmony_ci Column({ space: 5 }) { 608e41f4b71Sopenharmony_ci List({ space: 20, initialIndex: 19, scroller: this.scroller }) { 609e41f4b71Sopenharmony_ci Repeat(this.arrayHolder.arr) 610e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: this.totalCount }) 611e41f4b71Sopenharmony_ci .templateId((item, index) => { 612e41f4b71Sopenharmony_ci return 'number'; 613e41f4b71Sopenharmony_ci }) 614e41f4b71Sopenharmony_ci .template('number', (r) => { 615e41f4b71Sopenharmony_ci ListItem() { 616e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "Reuse"); 617e41f4b71Sopenharmony_ci } 618e41f4b71Sopenharmony_ci }) 619e41f4b71Sopenharmony_ci .each((r) => { 620e41f4b71Sopenharmony_ci ListItem() { 621e41f4b71Sopenharmony_ci Text(r.index! + ":" + r.item + "eachMessage"); 622e41f4b71Sopenharmony_ci } 623e41f4b71Sopenharmony_ci }) 624e41f4b71Sopenharmony_ci } 625e41f4b71Sopenharmony_ci .onScrollIndex((start, end) => { 626e41f4b71Sopenharmony_ci this.start = start; 627e41f4b71Sopenharmony_ci this.end = end; 628e41f4b71Sopenharmony_ci }) 629e41f4b71Sopenharmony_ci .height('30%') 630e41f4b71Sopenharmony_ci 631e41f4b71Sopenharmony_ci Button(`insert totalCount ${this.totalCount}`) 632e41f4b71Sopenharmony_ci .height(60) 633e41f4b71Sopenharmony_ci .onClick(() => { 634e41f4b71Sopenharmony_ci // Insert an element which locates in the previous position displayed on the screen. 635e41f4b71Sopenharmony_ci this.arrayHolder.arr.splice(18, 0, this.totalCount); 636e41f4b71Sopenharmony_ci let rect = this.scroller.getItemRect(this.start); // Obtain the size and position of the child component. 637e41f4b71Sopenharmony_ci this.scroller.scrollToIndex(this.start + 1); // Slide to the specified index. 638e41f4b71Sopenharmony_ci this.scroller.scrollBy(0, -rect.y); // Slide by a specified distance. 639e41f4b71Sopenharmony_ci this.totalCount = this.arrayHolder.arr.length; 640e41f4b71Sopenharmony_ci }) 641e41f4b71Sopenharmony_ci } 642e41f4b71Sopenharmony_ci .width('100%') 643e41f4b71Sopenharmony_ci .margin({ top: 5 }) 644e41f4b71Sopenharmony_ci } 645e41f4b71Sopenharmony_ci} 646e41f4b71Sopenharmony_ci``` 647e41f4b71Sopenharmony_ci 648e41f4b71Sopenharmony_ciThe figure below shows the effect. 649e41f4b71Sopenharmony_ci 650e41f4b71Sopenharmony_ci 651e41f4b71Sopenharmony_ci 652e41f4b71Sopenharmony_ci### The totalCount Value Is Greater Than the Length of Data Source 653e41f4b71Sopenharmony_ci 654e41f4b71Sopenharmony_ciWhen 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**. 655e41f4b71Sopenharmony_ci 656e41f4b71Sopenharmony_ciWhen 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. 657e41f4b71Sopenharmony_ci 658e41f4b71Sopenharmony_ciYou 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: 659e41f4b71Sopenharmony_ci 660e41f4b71Sopenharmony_ci```ts 661e41f4b71Sopenharmony_ci@ObservedV2 662e41f4b71Sopenharmony_ciclass VehicleData { 663e41f4b71Sopenharmony_ci @Trace name: string; 664e41f4b71Sopenharmony_ci @Trace price: number; 665e41f4b71Sopenharmony_ci 666e41f4b71Sopenharmony_ci constructor(name: string, price: number) { 667e41f4b71Sopenharmony_ci this.name = name; 668e41f4b71Sopenharmony_ci this.price = price; 669e41f4b71Sopenharmony_ci } 670e41f4b71Sopenharmony_ci} 671e41f4b71Sopenharmony_ci 672e41f4b71Sopenharmony_ci@ObservedV2 673e41f4b71Sopenharmony_ciclass VehicleDB { 674e41f4b71Sopenharmony_ci public vehicleItems: VehicleData[] = []; 675e41f4b71Sopenharmony_ci 676e41f4b71Sopenharmony_ci constructor() { 677e41f4b71Sopenharmony_ci // init data size 20 678e41f4b71Sopenharmony_ci for (let i = 1; i <= 20; i++) { 679e41f4b71Sopenharmony_ci this.vehicleItems.push(new VehicleData(`Vehicle${i}`, i)); 680e41f4b71Sopenharmony_ci } 681e41f4b71Sopenharmony_ci } 682e41f4b71Sopenharmony_ci} 683e41f4b71Sopenharmony_ci 684e41f4b71Sopenharmony_ci@Entry 685e41f4b71Sopenharmony_ci@ComponentV2 686e41f4b71Sopenharmony_cistruct entryCompSucc { 687e41f4b71Sopenharmony_ci @Local vehicleItems: VehicleData[] = new VehicleDB().vehicleItems; 688e41f4b71Sopenharmony_ci @Local listChildrenSize: ChildrenMainSize = new ChildrenMainSize(60); 689e41f4b71Sopenharmony_ci @Local totalCount: number = this.vehicleItems.length; 690e41f4b71Sopenharmony_ci scroller: Scroller = new Scroller(); 691e41f4b71Sopenharmony_ci 692e41f4b71Sopenharmony_ci build() { 693e41f4b71Sopenharmony_ci Column({ space: 3 }) { 694e41f4b71Sopenharmony_ci List({ scroller: this.scroller }) { 695e41f4b71Sopenharmony_ci Repeat(this.vehicleItems) 696e41f4b71Sopenharmony_ci .virtualScroll({ totalCount: 50 }) // total data size 50 697e41f4b71Sopenharmony_ci .templateId(() => 'default') 698e41f4b71Sopenharmony_ci .template('default', (ri) => { 699e41f4b71Sopenharmony_ci ListItem() { 700e41f4b71Sopenharmony_ci Column() { 701e41f4b71Sopenharmony_ci Text(`${ri.item.name} + ${ri.index}`) 702e41f4b71Sopenharmony_ci .width('90%') 703e41f4b71Sopenharmony_ci .height(this.listChildrenSize.childDefaultSize) 704e41f4b71Sopenharmony_ci .backgroundColor(0xFFA07A) 705e41f4b71Sopenharmony_ci .textAlign(TextAlign.Center) 706e41f4b71Sopenharmony_ci .fontSize(20) 707e41f4b71Sopenharmony_ci .fontWeight(FontWeight.Bold) 708e41f4b71Sopenharmony_ci } 709e41f4b71Sopenharmony_ci }.border({ width: 1 }) 710e41f4b71Sopenharmony_ci }, { cachedCount: 5 }) 711e41f4b71Sopenharmony_ci .each((ri) => { 712e41f4b71Sopenharmony_ci ListItem() { 713e41f4b71Sopenharmony_ci Text("Wrong: " + `${ri.item.name} + ${ri.index}`) 714e41f4b71Sopenharmony_ci .width('90%') 715e41f4b71Sopenharmony_ci .height(this.listChildrenSize.childDefaultSize) 716e41f4b71Sopenharmony_ci .backgroundColor(0xFFA07A) 717e41f4b71Sopenharmony_ci .textAlign(TextAlign.Center) 718e41f4b71Sopenharmony_ci .fontSize(20) 719e41f4b71Sopenharmony_ci .fontWeight(FontWeight.Bold) 720e41f4b71Sopenharmony_ci }.border({ width: 1 }) 721e41f4b71Sopenharmony_ci }) 722e41f4b71Sopenharmony_ci .key((item, index) => `${index}:${item}`) 723e41f4b71Sopenharmony_ci } 724e41f4b71Sopenharmony_ci .height('50%') 725e41f4b71Sopenharmony_ci .margin({ top: 20 }) 726e41f4b71Sopenharmony_ci .childrenMainSize(this.listChildrenSize) 727e41f4b71Sopenharmony_ci .alignListItem(ListItemAlign.Center) 728e41f4b71Sopenharmony_ci .onScrollIndex((start, end) => { 729e41f4b71Sopenharmony_ci console.log('onScrollIndex', start, end); 730e41f4b71Sopenharmony_ci // lazy data loading 731e41f4b71Sopenharmony_ci if (this.vehicleItems.length < 50) { 732e41f4b71Sopenharmony_ci for (let i = 0; i < 10; i++) { 733e41f4b71Sopenharmony_ci if (this.vehicleItems.length < 50) { 734e41f4b71Sopenharmony_ci this.vehicleItems.push(new VehicleData("Vehicle_loaded", i)); 735e41f4b71Sopenharmony_ci } 736e41f4b71Sopenharmony_ci } 737e41f4b71Sopenharmony_ci } 738e41f4b71Sopenharmony_ci }) 739e41f4b71Sopenharmony_ci } 740e41f4b71Sopenharmony_ci } 741e41f4b71Sopenharmony_ci} 742e41f4b71Sopenharmony_ci``` 743e41f4b71Sopenharmony_ci 744e41f4b71Sopenharmony_ciThe figure below shows the effect. 745e41f4b71Sopenharmony_ci 746e41f4b71Sopenharmony_ci 747