1e41f4b71Sopenharmony_ci# Repeat:子组件复用
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ci>**说明:**
4e41f4b71Sopenharmony_ci>
5e41f4b71Sopenharmony_ci>Repeat从API version 12开始支持。
6e41f4b71Sopenharmony_ci>
7e41f4b71Sopenharmony_ci>当前状态管理(V2试用版)仍在逐步开发中,相关功能尚未成熟,建议开发者尝鲜试用。
8e41f4b71Sopenharmony_ci
9e41f4b71Sopenharmony_ciAPI参数说明见:[Repeat API参数说明](../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md)
10e41f4b71Sopenharmony_ci
11e41f4b71Sopenharmony_ciRepeat组件non-virtualScroll场景(不开启virtualScroll开关)中,Repeat基于数据源进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在Repeat父容器组件中的子组件。Repeat循环渲染和ForEach相比有两个区别,一是优化了部分更新场景下的渲染性能,二是组件生成函数的索引index由框架侧来维护。
12e41f4b71Sopenharmony_ci
13e41f4b71Sopenharmony_ciRepeat组件virtualScroll场景中,Repeat将从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件,必须与滚动类容器组件配合使用。当在滚动类容器组件中使用了Repeat,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会缓存组件,并在下一次迭代中使用。
14e41f4b71Sopenharmony_ci
15e41f4b71Sopenharmony_ci> **注意:**
16e41f4b71Sopenharmony_ci>
17e41f4b71Sopenharmony_ci> Repeat组件的virtualScroll场景不完全兼容V1装饰器,使用V1装饰器存在渲染异常,不建议开发者同时使用。
18e41f4b71Sopenharmony_ci
19e41f4b71Sopenharmony_ci## 使用限制
20e41f4b71Sopenharmony_ci
21e41f4b71Sopenharmony_ci- Repeat使用键值作为标识,因此键值生成函数`key()`必须针对每个数据生成唯一的值。
22e41f4b71Sopenharmony_ci- Repeat virtualScroll场景必须在滚动类容器组件内使用,仅有[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)、[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)、[Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md)以及[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)组件支持virtualScroll场景(此时配置cachedCount会生效)。其它容器组件只适用于non-virtualScroll场景。
23e41f4b71Sopenharmony_ci- Repeat开启virtualScroll后,在每次迭代中,必须创建且只允许创建一个子组件。不开启virtualScroll没有该限制。生成的子组件必须是允许包含在Repeat父容器组件中的子组件。
24e41f4b71Sopenharmony_ci- 当Repeat与@Builder混用时,必须将RepeatItem类型整体进行传参,组件才能监听到数据变化,如果只传递`RepeatItem.item`或`RepeatItem.index`,将会出现UI渲染异常。
25e41f4b71Sopenharmony_ci- template模板目前只支持virtualScroll场景。当多个template type相同时,Repeat会覆盖旧的`template()`函数,仅生效最新的`template()`。
26e41f4b71Sopenharmony_ci- totalCount > array.length时,在父组件容器滚动过程中,应用需要保证列表即将滑动到数据源末尾时请求后续数据,直到数据源全部加载完成,否则列表滑动的过程中会出现滚动效果异常。解决方案见[totalCount值大于数据源长度](#totalcount值大于数据源长度)。
27e41f4b71Sopenharmony_ci
28e41f4b71Sopenharmony_ci## 键值生成规则
29e41f4b71Sopenharmony_ci
30e41f4b71Sopenharmony_ci键值生成函数`key()`的目的是允许Repeat识别数组更改的细节:添加了哪些数据、删除了哪些数据,以及哪些数据改变了位置(索引)。
31e41f4b71Sopenharmony_ci
32e41f4b71Sopenharmony_ci开发者使用建议:
33e41f4b71Sopenharmony_ci
34e41f4b71Sopenharmony_ci- 即使数据项有重复,开发者也必须保证键值key唯一(即使数据源发生变化);
35e41f4b71Sopenharmony_ci- 每次执行`key()`函数时,使用相同的数据项作为输入,输出必须是一致的;
36e41f4b71Sopenharmony_ci- `key()`中使用index是允许的,但不建议这样使用。原因是数据项移动时索引发生变化,即键值发生变化。因此Repeat会认为数据项发生了变化,并触发UI重新渲染,会降低性能表现;
37e41f4b71Sopenharmony_ci- 推荐将简单类型数组转换为类对象数组,并添加一个`readonly id`属性,在构造函数中给它赋一个唯一的值。
38e41f4b71Sopenharmony_ci
39e41f4b71Sopenharmony_ci### non-virtualScroll规则
40e41f4b71Sopenharmony_ci
41e41f4b71Sopenharmony_ci`key()`可以缺省,Repeat会生成默认key值。
42e41f4b71Sopenharmony_ci
43e41f4b71Sopenharmony_ci![Repeat-NonVS-KeyGen](./figures/Repeat-NonVS-KeyGen.png)
44e41f4b71Sopenharmony_ci
45e41f4b71Sopenharmony_ci### virtualScroll规则
46e41f4b71Sopenharmony_ci
47e41f4b71Sopenharmony_ci和non-virtualScroll的键值生成规则基本一致,`key()`可以缺省。
48e41f4b71Sopenharmony_ci
49e41f4b71Sopenharmony_ci![Repeat-VS-KeyGen](./figures/Repeat-VS-KeyGen.png)
50e41f4b71Sopenharmony_ci
51e41f4b71Sopenharmony_ci## 组件生成及复用规则
52e41f4b71Sopenharmony_ci
53e41f4b71Sopenharmony_ci### non-virtualScroll规则
54e41f4b71Sopenharmony_ci
55e41f4b71Sopenharmony_ci![Repeat-NonVS-FuncGen](./figures/Repeat-NonVS-FuncGen.png)
56e41f4b71Sopenharmony_ci
57e41f4b71Sopenharmony_ci子组件在Repeat首次渲染时全部创建,在数据更新时会对原组件进行复用。
58e41f4b71Sopenharmony_ci
59e41f4b71Sopenharmony_ci在Repeat组件进行数据更新时,它会依次对比上次的所有键值和本次更新之后的区别。若当前键值和上次的某一项键值相同,Repeat会直接复用子组件并对RepeatItem.index索引做对应的更新。
60e41f4b71Sopenharmony_ci
61e41f4b71Sopenharmony_ci当Repeat将所有重复的键值对比完并做了相应的复用后,若上次的键值有不重复的且本次更新之后有新的键值生成需要新建子组件时,Repeat会复用上次多余的子组件并更新RepeatItem.item数据源和RepeatItem.index索引并刷新UI。
62e41f4b71Sopenharmony_ci
63e41f4b71Sopenharmony_ci若上次的剩余>=本次新更新的数量,则组件完全复用并释放多余的未被复用的组件。若上次的剩余小于本次新更新的数量,将剩余的组件复用完后,Repeat会新建多出来的数据项对应的组件。
64e41f4b71Sopenharmony_ci
65e41f4b71Sopenharmony_ci### virtualScroll规则
66e41f4b71Sopenharmony_ci
67e41f4b71Sopenharmony_ci子组件在Repeat首次渲染只生成当前需要的组件,在滑动和数据更新时会缓存下屏的节点,在需要生成新的组件时,对缓存里的组件进行复用。
68e41f4b71Sopenharmony_ci
69e41f4b71Sopenharmony_ci#### 滑动场景
70e41f4b71Sopenharmony_ci
71e41f4b71Sopenharmony_ci滑动前节点现状如下图所示
72e41f4b71Sopenharmony_ci
73e41f4b71Sopenharmony_ci![Repeat-Start](./figures/Repeat-Start.PNG)
74e41f4b71Sopenharmony_ci
75e41f4b71Sopenharmony_ci当前Repeat组件templateId有a和b两种,templateId a对应的缓存池,其最大缓存值为3,templateId b对应的缓存池,其最大缓存值为4,其父组件默认预加载节点1个。这时,我们将屏幕右滑,Repeat将开始复用缓存池中的节点。
76e41f4b71Sopenharmony_ci
77e41f4b71Sopenharmony_ci![Repeat-Slide](./figures/Repeat-Slide.PNG)
78e41f4b71Sopenharmony_ci
79e41f4b71Sopenharmony_ciindex=18的数据进入屏幕及父组件预加载的范围内,此时计算出其templateId为b,这时Repeat会从type=b的缓存池中取出一个节点进行复用,更新它的key&index&data,该子节点内部使用了该项数据及索引的其他孙子节点会根据V2状态管理的规则做同步更新。
80e41f4b71Sopenharmony_ci
81e41f4b71Sopenharmony_ciindex=10的节点划出了屏幕及父组件预加载的范围。当UI主线程空闲时,会去检测type=a的缓存池是否还有空间,此时缓存池中有四个节点,超过了额定的3个,Repeat会释放掉最后一个节点。
82e41f4b71Sopenharmony_ci
83e41f4b71Sopenharmony_ci![Repeat-Slide-Done](./figures/Repeat-Slide-Done.PNG)
84e41f4b71Sopenharmony_ci
85e41f4b71Sopenharmony_ci#### 数据更新场景
86e41f4b71Sopenharmony_ci
87e41f4b71Sopenharmony_ci![Repeat-Start](./figures/Repeat-Start.PNG)
88e41f4b71Sopenharmony_ci
89e41f4b71Sopenharmony_ci此时我们做如下更新操作,删除index=12节点,更新index=13节点的数据,更新index=14节点的templateId为a,更新index=15节点的key。
90e41f4b71Sopenharmony_ci
91e41f4b71Sopenharmony_ci![Repeat-Update1](./figures/Repeat-Update1.PNG)
92e41f4b71Sopenharmony_ci
93e41f4b71Sopenharmony_ci此时Repeat会通知父组件重新布局,逐一对比templateId值,若和原节点templateId值相同,则复用该节点,更新key、index和data,若templateId值发生变化,则复用相应的templateId缓存池中的节点,并更新key、index和data。
94e41f4b71Sopenharmony_ci
95e41f4b71Sopenharmony_ci![Repeat-Update2](./figures/Repeat-Update2.PNG)
96e41f4b71Sopenharmony_ci
97e41f4b71Sopenharmony_ci上图显示node13节点更新了数据data和index;node14更新了templateId和index,于是从缓存池中取走一个复用;node15由于key值发生变化并且templateId不变,复用自身节点并同步更新key、index、data;node16和node17均只更新index。index=17的节点是新的,从缓存池中复用。
98e41f4b71Sopenharmony_ci
99e41f4b71Sopenharmony_ci![Repeat-Update-Done](./figures/Repeat-Update-Done.PNG)
100e41f4b71Sopenharmony_ci
101e41f4b71Sopenharmony_ci## totalCount规则
102e41f4b71Sopenharmony_ci
103e41f4b71Sopenharmony_ci数据源的总长度,可以大于已加载数据项的数量。令arr.length表示数据源长度,以下为totalCount的处理规则:
104e41f4b71Sopenharmony_ci
105e41f4b71Sopenharmony_ci- totalCount缺省/非自然数时,totalCount默认为arr.length,列表正常滚动;
106e41f4b71Sopenharmony_ci- 0 <= totalCount < arr.length时,界面中只渲染“totalCount”个数据;
107e41f4b71Sopenharmony_ci- totalCount > arr.length时,代表Repeat将渲染totalCount个数据,滚动条样式根据totalCount值变化。
108e41f4b71Sopenharmony_ci
109e41f4b71Sopenharmony_ci> **注意:** 当totalCount < arr.length时,在父组件容器滚动过程中,应用需要保证列表即将滑动到数据源末尾时请求后续数据,开发者需要对数据请求的错误场景(如网络延迟)进行保护操作,直到数据源全部加载完成,否则列表滑动的过程中会出现滚动效果异常。
110e41f4b71Sopenharmony_ci
111e41f4b71Sopenharmony_ci## cachedCount规则
112e41f4b71Sopenharmony_ci
113e41f4b71Sopenharmony_cicachedCount是当前模板在Repeat的缓存池中可缓存子节点的最大数量,仅在virtualScroll场景下生效。
114e41f4b71Sopenharmony_ci
115e41f4b71Sopenharmony_ci首先需要明确滚动类容器组件 `.cachedCount()`属性方法和Repeat `cachedCount`的区别。这两者都是为了平衡性能和内存,但是其含义是不同的。
116e41f4b71Sopenharmony_ci
117e41f4b71Sopenharmony_ci- 滚动类容器组件 `.cachedCount()`:是指在可见范围外预加载的节点,这些节点会位于组件树上,但不是可见范围内,List/Grid等容器组件会额外渲染这些可见范围外的节点,从而达到其性能收益。Repeat会将这些节点视为“可见”的。
118e41f4b71Sopenharmony_ci- Repeat `cachedCount`: 是指Repeat视为“不可见”的节点,这些节点是空闲的,框架会暂时保存,在需要使用的时候更新这些节点,从而实现复用。
119e41f4b71Sopenharmony_ci
120e41f4b71Sopenharmony_ci将cachedCount设置为当前模板的节点在屏上可能出现的最大数量时,Repeat可以做到尽可能多的复用。但后果是当屏上没有当前模板的节点时,缓存池也不会释放,应用内存会增大。需要开发者依据具体情况自行把控。
121e41f4b71Sopenharmony_ci
122e41f4b71Sopenharmony_ci- cachedCount缺省时,框架会分别对不同template,根据屏上节点+预加载的节点个数来计算cachedCount。当屏上节点+预加载的节点个数变多时,cachedCount也会对应增长。需要注意cachedCount数量不会减少。
123e41f4b71Sopenharmony_ci- 显式指定cachedCount,推荐设置成和屏幕上节点个数一致。需要注意,不推荐设置cachedCount小于2,因为这会导致在快速滑动场景下创建新的节点,从而导致性能劣化。
124e41f4b71Sopenharmony_ci
125e41f4b71Sopenharmony_ci
126e41f4b71Sopenharmony_ci
127e41f4b71Sopenharmony_ci## 使用场景
128e41f4b71Sopenharmony_ci
129e41f4b71Sopenharmony_ci### non-virtualScroll
130e41f4b71Sopenharmony_ci
131e41f4b71Sopenharmony_ci#### 数据源变化
132e41f4b71Sopenharmony_ci
133e41f4b71Sopenharmony_ci在Repeat组件进行非首次渲染时,它会依次对比上次的所有键值和本次更新之后的区别。若当前键值和上次的某一项键值相同,Repeat会直接复用子组件并对RepeatItem.index索引做对应的更新。
134e41f4b71Sopenharmony_ci
135e41f4b71Sopenharmony_ci当Repeat将所有重复的键值对比完并做了相应的复用后,若上次的键值有不重复的且本次更新之后有新的键值生成需要新建子组件时,Repeat会复用上次多余的子组件并更新RepeatItem.item数据源和RepeatItem.index索引。
136e41f4b71Sopenharmony_ci
137e41f4b71Sopenharmony_ci若上次的剩余>=本次新更新的数量,则组件完全复用,若上次的剩余小于本次新更新的数量,将剩余的组件复用完后,Repeat会新建多出来的数据项对应的组件。
138e41f4b71Sopenharmony_ci
139e41f4b71Sopenharmony_ci```ts
140e41f4b71Sopenharmony_ci@Entry
141e41f4b71Sopenharmony_ci@ComponentV2
142e41f4b71Sopenharmony_cistruct Parent {
143e41f4b71Sopenharmony_ci  @Local simpleList: Array<string> = ['one', 'two', 'three'];
144e41f4b71Sopenharmony_ci
145e41f4b71Sopenharmony_ci  build() {
146e41f4b71Sopenharmony_ci    Row() {
147e41f4b71Sopenharmony_ci      Column() {
148e41f4b71Sopenharmony_ci        Text('点击修改第3个数组项的值')
149e41f4b71Sopenharmony_ci          .fontSize(24)
150e41f4b71Sopenharmony_ci          .fontColor(Color.Red)
151e41f4b71Sopenharmony_ci          .onClick(() => {
152e41f4b71Sopenharmony_ci            this.simpleList[2] = 'new three';
153e41f4b71Sopenharmony_ci          })
154e41f4b71Sopenharmony_ci
155e41f4b71Sopenharmony_ci        Repeat<string>(this.simpleList)
156e41f4b71Sopenharmony_ci            .each((obj: RepeatItem<string>)=>{
157e41f4b71Sopenharmony_ci              ChildItem({ item: obj.item })
158e41f4b71Sopenharmony_ci                .margin({top: 20})
159e41f4b71Sopenharmony_ci            })
160e41f4b71Sopenharmony_ci            .key((item: string) => item)
161e41f4b71Sopenharmony_ci      }
162e41f4b71Sopenharmony_ci      .justifyContent(FlexAlign.Center)
163e41f4b71Sopenharmony_ci      .width('100%')
164e41f4b71Sopenharmony_ci      .height('100%')
165e41f4b71Sopenharmony_ci    }
166e41f4b71Sopenharmony_ci    .height('100%')
167e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
168e41f4b71Sopenharmony_ci  }
169e41f4b71Sopenharmony_ci}
170e41f4b71Sopenharmony_ci
171e41f4b71Sopenharmony_ci@ComponentV2
172e41f4b71Sopenharmony_cistruct ChildItem {
173e41f4b71Sopenharmony_ci  @Param @Require item: string;
174e41f4b71Sopenharmony_ci
175e41f4b71Sopenharmony_ci  build() {
176e41f4b71Sopenharmony_ci    Text(this.item)
177e41f4b71Sopenharmony_ci      .fontSize(30)
178e41f4b71Sopenharmony_ci  }
179e41f4b71Sopenharmony_ci}
180e41f4b71Sopenharmony_ci```
181e41f4b71Sopenharmony_ci
182e41f4b71Sopenharmony_ci![ForEach-Non-Initial-Render-Case-Effect](./figures/ForEach-Non-Initial-Render-Case-Effect.gif)
183e41f4b71Sopenharmony_ci
184e41f4b71Sopenharmony_ci第三个数组项重新渲染时会复用之前的第三项的组件,仅对数据做了刷新。
185e41f4b71Sopenharmony_ci
186e41f4b71Sopenharmony_ci#### 索引值变化
187e41f4b71Sopenharmony_ci
188e41f4b71Sopenharmony_ci下方例子当我们交换数组项1和2时,若键值和上次保持一致,Repeat会复用之前的组件,仅对使用了index索引值的组件做数据刷新。
189e41f4b71Sopenharmony_ci
190e41f4b71Sopenharmony_ci```ts
191e41f4b71Sopenharmony_ci@Entry
192e41f4b71Sopenharmony_ci@ComponentV2
193e41f4b71Sopenharmony_cistruct Parent {
194e41f4b71Sopenharmony_ci  @Local simpleList: Array<string> = ['one', 'two', 'three'];
195e41f4b71Sopenharmony_ci
196e41f4b71Sopenharmony_ci  build() {
197e41f4b71Sopenharmony_ci    Row() {
198e41f4b71Sopenharmony_ci      Column() {
199e41f4b71Sopenharmony_ci        Text('交换数组项1,2')
200e41f4b71Sopenharmony_ci          .fontSize(24)
201e41f4b71Sopenharmony_ci          .fontColor(Color.Red)
202e41f4b71Sopenharmony_ci          .onClick(() => {
203e41f4b71Sopenharmony_ci            let temp: string = this.simpleList[2]
204e41f4b71Sopenharmony_ci            this.simpleList[2] = this.simpleList[1]
205e41f4b71Sopenharmony_ci            this.simpleList[1] = temp
206e41f4b71Sopenharmony_ci          })
207e41f4b71Sopenharmony_ci          .margin({bottom: 20})
208e41f4b71Sopenharmony_ci
209e41f4b71Sopenharmony_ci        Repeat<string>(this.simpleList)
210e41f4b71Sopenharmony_ci          .each((obj: RepeatItem<string>)=>{
211e41f4b71Sopenharmony_ci            Text("index: " + obj.index)
212e41f4b71Sopenharmony_ci              .fontSize(30)
213e41f4b71Sopenharmony_ci            ChildItem({ item: obj.item })
214e41f4b71Sopenharmony_ci              .margin({bottom: 20})
215e41f4b71Sopenharmony_ci          })
216e41f4b71Sopenharmony_ci          .key((item: string) => item)
217e41f4b71Sopenharmony_ci      }
218e41f4b71Sopenharmony_ci      .justifyContent(FlexAlign.Center)
219e41f4b71Sopenharmony_ci      .width('100%')
220e41f4b71Sopenharmony_ci      .height('100%')
221e41f4b71Sopenharmony_ci    }
222e41f4b71Sopenharmony_ci    .height('100%')
223e41f4b71Sopenharmony_ci    .backgroundColor(0xF1F3F5)
224e41f4b71Sopenharmony_ci  }
225e41f4b71Sopenharmony_ci}
226e41f4b71Sopenharmony_ci
227e41f4b71Sopenharmony_ci@ComponentV2
228e41f4b71Sopenharmony_cistruct ChildItem {
229e41f4b71Sopenharmony_ci  @Param @Require item: string;
230e41f4b71Sopenharmony_ci
231e41f4b71Sopenharmony_ci  build() {
232e41f4b71Sopenharmony_ci    Text(this.item)
233e41f4b71Sopenharmony_ci      .fontSize(30)
234e41f4b71Sopenharmony_ci  }
235e41f4b71Sopenharmony_ci}
236e41f4b71Sopenharmony_ci```
237e41f4b71Sopenharmony_ci
238e41f4b71Sopenharmony_ci![Repeat-Non-Initial-Render-Case-Exchange-Effect](./figures/Repeat-Non-Initial-Render-Case-Exchange-Effect.gif)
239e41f4b71Sopenharmony_ci
240e41f4b71Sopenharmony_ci### virtualScroll
241e41f4b71Sopenharmony_ci
242e41f4b71Sopenharmony_ci本小节将展示virtualScroll场景下,Repeat的实际使用场景和组件节点的复用情况。根据复用规则可以衍生出大量的测试场景,篇幅原因,只对典型的数据变化进行解释。
243e41f4b71Sopenharmony_ci
244e41f4b71Sopenharmony_ci#### 应用示例
245e41f4b71Sopenharmony_ci
246e41f4b71Sopenharmony_ci下面的代码设计了Repeat组件的virtualScroll场景典型数据源操作,包括**插入数据、修改数据、删除数据、交换数据**。点击相应的文字可以触发数据的变化,依次点击数据项可以交换被点击的两个数据项。
247e41f4b71Sopenharmony_ci
248e41f4b71Sopenharmony_ci```ts
249e41f4b71Sopenharmony_ci@ObservedV2
250e41f4b71Sopenharmony_ciclass Clazz {
251e41f4b71Sopenharmony_ci  @Trace message: string = '';
252e41f4b71Sopenharmony_ci
253e41f4b71Sopenharmony_ci  constructor(message: string) {
254e41f4b71Sopenharmony_ci    this.message = message;
255e41f4b71Sopenharmony_ci  }
256e41f4b71Sopenharmony_ci}
257e41f4b71Sopenharmony_ci
258e41f4b71Sopenharmony_ci@Entry
259e41f4b71Sopenharmony_ci@ComponentV2
260e41f4b71Sopenharmony_cistruct TestPage {
261e41f4b71Sopenharmony_ci  @Local simpleList: Array<Clazz> = [];
262e41f4b71Sopenharmony_ci  private exchange: number[] = [];
263e41f4b71Sopenharmony_ci  private counter: number = 0;
264e41f4b71Sopenharmony_ci
265e41f4b71Sopenharmony_ci  aboutToAppear(): void {
266e41f4b71Sopenharmony_ci    for (let i = 0; i < 100; i++) {
267e41f4b71Sopenharmony_ci      this.simpleList.push(new Clazz('Hello ' + i));
268e41f4b71Sopenharmony_ci    }
269e41f4b71Sopenharmony_ci  }
270e41f4b71Sopenharmony_ci
271e41f4b71Sopenharmony_ci  build() {
272e41f4b71Sopenharmony_ci    Column({ space: 10 }) {
273e41f4b71Sopenharmony_ci      Text('点击插入第5项')
274e41f4b71Sopenharmony_ci        .fontSize(24)
275e41f4b71Sopenharmony_ci        .fontColor(Color.Red)
276e41f4b71Sopenharmony_ci        .onClick(() => {
277e41f4b71Sopenharmony_ci          this.simpleList.splice(4, 0, new Clazz(`${this.counter++}_new item`));
278e41f4b71Sopenharmony_ci        })
279e41f4b71Sopenharmony_ci      Text('点击修改第5项')
280e41f4b71Sopenharmony_ci        .fontSize(24)
281e41f4b71Sopenharmony_ci        .fontColor(Color.Red)
282e41f4b71Sopenharmony_ci        .onClick(() => {
283e41f4b71Sopenharmony_ci          this.simpleList[4].message = `${this.counter++}_new item`;
284e41f4b71Sopenharmony_ci        })
285e41f4b71Sopenharmony_ci      Text('点击删除第5项')
286e41f4b71Sopenharmony_ci        .fontSize(24)
287e41f4b71Sopenharmony_ci        .fontColor(Color.Red)
288e41f4b71Sopenharmony_ci        .onClick(() => {
289e41f4b71Sopenharmony_ci          this.simpleList.splice(4, 1);
290e41f4b71Sopenharmony_ci        })
291e41f4b71Sopenharmony_ci      Text('依次点击两个数据项进行交换')
292e41f4b71Sopenharmony_ci        .fontSize(24)
293e41f4b71Sopenharmony_ci        .fontColor(Color.Red)
294e41f4b71Sopenharmony_ci
295e41f4b71Sopenharmony_ci      List({ initialIndex: 10 }) {
296e41f4b71Sopenharmony_ci        Repeat<Clazz>(this.simpleList)
297e41f4b71Sopenharmony_ci          .each((obj: RepeatItem<Clazz>) => {
298e41f4b71Sopenharmony_ci            ListItem() {
299e41f4b71Sopenharmony_ci              Text('[each] ' + obj.item.message)
300e41f4b71Sopenharmony_ci                .fontSize(30)
301e41f4b71Sopenharmony_ci                .margin({ top: 10 })
302e41f4b71Sopenharmony_ci            }
303e41f4b71Sopenharmony_ci          })
304e41f4b71Sopenharmony_ci          .key((item: Clazz, index: number) => {
305e41f4b71Sopenharmony_ci            return item.message;
306e41f4b71Sopenharmony_ci          })
307e41f4b71Sopenharmony_ci          .virtualScroll({ totalCount: this.simpleList.length })
308e41f4b71Sopenharmony_ci          .templateId((item: Clazz, index: number) => "default")
309e41f4b71Sopenharmony_ci          .template('default', (ri) => {
310e41f4b71Sopenharmony_ci            Text('[template] ' + ri.item.message)
311e41f4b71Sopenharmony_ci              .fontSize(30)
312e41f4b71Sopenharmony_ci              .margin({ top: 10 })
313e41f4b71Sopenharmony_ci              .onClick(() => {
314e41f4b71Sopenharmony_ci                this.exchange.push(ri.index);
315e41f4b71Sopenharmony_ci                if (this.exchange.length === 2) {
316e41f4b71Sopenharmony_ci                  let _a = this.exchange[0];
317e41f4b71Sopenharmony_ci                  let _b = this.exchange[1];
318e41f4b71Sopenharmony_ci                  // click to exchange
319e41f4b71Sopenharmony_ci                  let temp: string = this.simpleList[_a].message;
320e41f4b71Sopenharmony_ci                  this.simpleList[_a].message = this.simpleList[_b].message;
321e41f4b71Sopenharmony_ci                  this.simpleList[_b].message = temp;
322e41f4b71Sopenharmony_ci                  this.exchange = [];
323e41f4b71Sopenharmony_ci                }
324e41f4b71Sopenharmony_ci              })
325e41f4b71Sopenharmony_ci          }, { cachedCount: 3 })
326e41f4b71Sopenharmony_ci      }
327e41f4b71Sopenharmony_ci      .cachedCount(1)
328e41f4b71Sopenharmony_ci      .border({ width: 1 })
329e41f4b71Sopenharmony_ci      .width('90%')
330e41f4b71Sopenharmony_ci      .height('70%')
331e41f4b71Sopenharmony_ci    }
332e41f4b71Sopenharmony_ci    .height('100%')
333e41f4b71Sopenharmony_ci    .justifyContent(FlexAlign.Center)
334e41f4b71Sopenharmony_ci  }
335e41f4b71Sopenharmony_ci}
336e41f4b71Sopenharmony_ci```
337e41f4b71Sopenharmony_ci该应用列表内容为100项自定义类`Clazz`的`message`字符串属性,List组件的cachedCount设为1,template “default”缓存池大小设为3。应用界面如下图所示:
338e41f4b71Sopenharmony_ci
339e41f4b71Sopenharmony_ci![Repeat-VirtualScroll-Demo](./figures/Repeat-VirtualScroll-Demo.jpg)
340e41f4b71Sopenharmony_ci
341e41f4b71Sopenharmony_ci#### 节点操作实例
342e41f4b71Sopenharmony_ci
343e41f4b71Sopenharmony_ci当进行数据源变化操作时,key值改变的节点会被重新创建。如果相对应的template的缓存池中有缓存节点,就会进行节点复用。当key值不变时,组件会直接复用并更新index的值。
344e41f4b71Sopenharmony_ci
345e41f4b71Sopenharmony_ci**插入数据**
346e41f4b71Sopenharmony_ci
347e41f4b71Sopenharmony_ci数据操作:
348e41f4b71Sopenharmony_ci
349e41f4b71Sopenharmony_ci![Repeat-VirtualScroll-InsertData](./figures/Repeat-VirtualScroll-InsertData.gif)
350e41f4b71Sopenharmony_ci
351e41f4b71Sopenharmony_ci本例做了四次插入数据操作,前两次为屏幕上方插入数据,后两次为当前屏幕插入数据。打印onUpdateNode函数执行情况“[旧节点key值] -> [新节点key值]”,代表“旧节点”复用“新节点”。节点复用情况如下:
352e41f4b71Sopenharmony_ci
353e41f4b71Sopenharmony_ci```
354e41f4b71Sopenharmony_ci// 屏幕上方两次插入
355e41f4b71Sopenharmony_cionUpdateNode [Hello 22] -> [Hello 8]
356e41f4b71Sopenharmony_cionUpdateNode [Hello 21] -> [Hello 7]
357e41f4b71Sopenharmony_ci// 当前屏幕两次插入
358e41f4b71Sopenharmony_cionUpdateNode [Hello 11] -> [2_new item]
359e41f4b71Sopenharmony_cionUpdateNode [Hello 10] -> [3_new item]
360e41f4b71Sopenharmony_ci```
361e41f4b71Sopenharmony_ci
362e41f4b71Sopenharmony_ci在屏幕上方插入数据时,会发生节点移动,引起当前屏幕的预加载节点改变,预加载节点发生了复用,即下方出缓存的节点22复用给了上方进入缓存的节点8。在当前屏幕插入数据时,会产生新数据项,新的节点会复用屏幕下方出缓存的预加载节点。本应用中屏幕下方添加数据时不会发生复用。
363e41f4b71Sopenharmony_ci
364e41f4b71Sopenharmony_ci**修改数据**
365e41f4b71Sopenharmony_ci
366e41f4b71Sopenharmony_ci数据操作:
367e41f4b71Sopenharmony_ci
368e41f4b71Sopenharmony_ci![Repeat-VirtualScroll-ModifyData](./figures/Repeat-VirtualScroll-ModifyData.gif)
369e41f4b71Sopenharmony_ci
370e41f4b71Sopenharmony_ci本例做了四次修改数据操作,前两次为屏幕上方修改数据,后两次为当前屏幕修改数据。打印onUpdateNode函数执行情况“[旧节点key值] -> [新节点key值]”,代表“旧节点”复用“新节点”。节点复用情况如下:
371e41f4b71Sopenharmony_ci
372e41f4b71Sopenharmony_ci```
373e41f4b71Sopenharmony_ci// 当前屏幕两次修改
374e41f4b71Sopenharmony_cionUpdateNode [1_new item] -> [2_new item]
375e41f4b71Sopenharmony_cionUpdateNode [2_new item] -> [3_new item]
376e41f4b71Sopenharmony_ci```
377e41f4b71Sopenharmony_ci
378e41f4b71Sopenharmony_ci由于屏幕上方/下方的数据不存在渲染节点,所以不会发生节点复用。在当前屏幕修改节点时,由于节点templateId值没有改变,所以复用自身节点,节点id不变。
379e41f4b71Sopenharmony_ci
380e41f4b71Sopenharmony_ci**交换数据**
381e41f4b71Sopenharmony_ci
382e41f4b71Sopenharmony_ci数据操作:
383e41f4b71Sopenharmony_ci
384e41f4b71Sopenharmony_ci![Repeat-VirtualScroll-ExchangeData](./figures/Repeat-VirtualScroll-ExchangeData.gif)
385e41f4b71Sopenharmony_ci
386e41f4b71Sopenharmony_ci本例在当前屏幕做了两次交换数据操作。由于key值未发生改变,直接交换两个节点,没有节点复用。
387e41f4b71Sopenharmony_ci
388e41f4b71Sopenharmony_ci**删除数据**
389e41f4b71Sopenharmony_ci
390e41f4b71Sopenharmony_ci数据操作:
391e41f4b71Sopenharmony_ci
392e41f4b71Sopenharmony_ci![Repeat-VirtualScroll-DeleteData](./figures/Repeat-VirtualScroll-DeleteData.gif)
393e41f4b71Sopenharmony_ci
394e41f4b71Sopenharmony_ci本例做了五次删除数据操作,前两次为屏幕上方删除数据,后三次为当前屏幕删除数据。打印onUpdateNode函数执行情况“[旧节点key值] -> [新节点key值]”,代表“旧节点”复用“新节点”。节点复用情况如下:
395e41f4b71Sopenharmony_ci
396e41f4b71Sopenharmony_ci```
397e41f4b71Sopenharmony_ci// 屏幕上方两次删除
398e41f4b71Sopenharmony_cionUpdateNode [Hello 9] -> [Hello 23]
399e41f4b71Sopenharmony_cionUpdateNode [Hello 10] -> [Hello 24]
400e41f4b71Sopenharmony_ci// 当前屏幕两次删除没有调用onUpdateNode
401e41f4b71Sopenharmony_ci// 当前屏幕第三次删除
402e41f4b71Sopenharmony_cionUpdateNode [Hello 6] -> [Hello 17]
403e41f4b71Sopenharmony_ci```
404e41f4b71Sopenharmony_ci
405e41f4b71Sopenharmony_ci在屏幕上方删除数据时,会发生节点移动,引起当前屏幕的预加载节点改变,预加载节点发生了复用,即上方出缓存的节点9复用给了下方进入缓存的节点23。当前屏幕删除数据时,由于List组件的cachedCount预加载属性,前两次删除操作中,进入屏幕的节点已经渲染,不会发生复用,被删除的节点进入对应template的缓存池中。第三次删除时,下方进入预加载缓存的节点17复用了缓存池中的节点6。
406e41f4b71Sopenharmony_ci
407e41f4b71Sopenharmony_ci#### 使用多个template
408e41f4b71Sopenharmony_ci
409e41f4b71Sopenharmony_ci```
410e41f4b71Sopenharmony_ci@ObservedV2
411e41f4b71Sopenharmony_ciclass Wrap1 {
412e41f4b71Sopenharmony_ci    @Trace message: string = '';
413e41f4b71Sopenharmony_ci    
414e41f4b71Sopenharmony_ci    constructor(message: string) {
415e41f4b71Sopenharmony_ci        this.message = message;
416e41f4b71Sopenharmony_ci    }
417e41f4b71Sopenharmony_ci}
418e41f4b71Sopenharmony_ci
419e41f4b71Sopenharmony_ci@Entry
420e41f4b71Sopenharmony_ci@ComponentV2
421e41f4b71Sopenharmony_cistruct Parent {
422e41f4b71Sopenharmony_ci    @Local simpleList: Array<Wrap1> = [];
423e41f4b71Sopenharmony_ci    
424e41f4b71Sopenharmony_ci    aboutToAppear(): void {
425e41f4b71Sopenharmony_ci        for (let i=0; i<100; i++) {
426e41f4b71Sopenharmony_ci            this.simpleList.push(new Wrap1('Hello' + i));
427e41f4b71Sopenharmony_ci        }
428e41f4b71Sopenharmony_ci    }
429e41f4b71Sopenharmony_ci    
430e41f4b71Sopenharmony_ci    build() {
431e41f4b71Sopenharmony_ci        Column() {
432e41f4b71Sopenharmony_ci            List() {
433e41f4b71Sopenharmony_ci                Repeat<Wrap1>(this.simpleList)
434e41f4b71Sopenharmony_ci                	.each((obj: RepeatItem<Wrap1>)=>{
435e41f4b71Sopenharmony_ci                    	ListItem() {
436e41f4b71Sopenharmony_ci                    		Row() {
437e41f4b71Sopenharmony_ci                    			Text('default index ' + obj.index + ': ')
438e41f4b71Sopenharmony_ci                            		.fontSize(30)
439e41f4b71Sopenharmony_ci                            	Text(obj.item.message)
440e41f4b71Sopenharmony_ci                            		.fontSize(30)
441e41f4b71Sopenharmony_ci                    		}
442e41f4b71Sopenharmony_ci                        }
443e41f4b71Sopenharmony_ci                        .margin(20)
444e41f4b71Sopenharmony_ci                	})
445e41f4b71Sopenharmony_ci                	.template('odd', (obj: RepeatItem<Wrap1>)=>{
446e41f4b71Sopenharmony_ci                    	ListItem() {
447e41f4b71Sopenharmony_ci                    		Row() {
448e41f4b71Sopenharmony_ci                    			Text('odd index ' + obj.index + ': ')
449e41f4b71Sopenharmony_ci                            		.fontSize(30)
450e41f4b71Sopenharmony_ci                            		.fontColor(Color.Blue)
451e41f4b71Sopenharmony_ci                            	Text(obj.item.message)
452e41f4b71Sopenharmony_ci                            		.fontSize(30)
453e41f4b71Sopenharmony_ci                            		.fontColor(Color.Blue)
454e41f4b71Sopenharmony_ci                    		}
455e41f4b71Sopenharmony_ci                        }
456e41f4b71Sopenharmony_ci                        .margin(20)
457e41f4b71Sopenharmony_ci                	})
458e41f4b71Sopenharmony_ci                	.template('even', (obj: RepeatItem<Wrap1>)=>{
459e41f4b71Sopenharmony_ci                    	ListItem() {
460e41f4b71Sopenharmony_ci                    		Row() {
461e41f4b71Sopenharmony_ci                    			Text('even index ' + obj.index + ': ')
462e41f4b71Sopenharmony_ci                            		.fontSize(30)
463e41f4b71Sopenharmony_ci                            		.fontColor(Color.Green)
464e41f4b71Sopenharmony_ci                            	Text(obj.item.message)
465e41f4b71Sopenharmony_ci                            		.fontSize(30)
466e41f4b71Sopenharmony_ci                            		.fontColor(Color.Green)
467e41f4b71Sopenharmony_ci                    		}
468e41f4b71Sopenharmony_ci                        }
469e41f4b71Sopenharmony_ci                        .margin(20)
470e41f4b71Sopenharmony_ci                	})
471e41f4b71Sopenharmony_ci                	.templateId((item: Wrap1, index: number) => {
472e41f4b71Sopenharmony_ci                		return index%2 ? 'odd' : 'even';
473e41f4b71Sopenharmony_ci                	})
474e41f4b71Sopenharmony_ci                	.key((item: Wrap1, index: number) => {
475e41f4b71Sopenharmony_ci                		return item.message;
476e41f4b71Sopenharmony_ci                	})
477e41f4b71Sopenharmony_ci            }
478e41f4b71Sopenharmony_ci            .cachedCount(5)
479e41f4b71Sopenharmony_ci            .width('100%')
480e41f4b71Sopenharmony_ci            .height('100%')
481e41f4b71Sopenharmony_ci        }
482e41f4b71Sopenharmony_ci        .height('100%')
483e41f4b71Sopenharmony_ci    }
484e41f4b71Sopenharmony_ci}
485e41f4b71Sopenharmony_ci```
486e41f4b71Sopenharmony_ci
487e41f4b71Sopenharmony_ci![Repeat-VirtualScroll-DataChange](./figures/Repeat-VirtualScroll-Template.gif)
488e41f4b71Sopenharmony_ci
489e41f4b71Sopenharmony_ci## 常见问题
490e41f4b71Sopenharmony_ci
491e41f4b71Sopenharmony_ci### 屏幕外的列表数据发生变化时,保证滚动条位置不变
492e41f4b71Sopenharmony_ci
493e41f4b71Sopenharmony_ci在List组件中声明Repeat组件,实现key值生成逻辑和each逻辑(如下示例代码),点击按钮“insert”,在屏幕显示的第一个元素前面插入一个元素,屏幕出现向下滚动。
494e41f4b71Sopenharmony_ci
495e41f4b71Sopenharmony_ci```ts
496e41f4b71Sopenharmony_ci// 定义一个类,标记为可观察的
497e41f4b71Sopenharmony_ci// 类中自定义一个数组,标记为可追踪的
498e41f4b71Sopenharmony_ci@ObservedV2
499e41f4b71Sopenharmony_ciclass ArrayHolder {
500e41f4b71Sopenharmony_ci  @Trace arr: Array<number> = [];
501e41f4b71Sopenharmony_ci
502e41f4b71Sopenharmony_ci  // constructor,用于初始化数组个数
503e41f4b71Sopenharmony_ci  constructor(count: number) {
504e41f4b71Sopenharmony_ci    for (let i = 0; i < count; i++) {
505e41f4b71Sopenharmony_ci      this.arr.push(i);
506e41f4b71Sopenharmony_ci    }
507e41f4b71Sopenharmony_ci  }
508e41f4b71Sopenharmony_ci}
509e41f4b71Sopenharmony_ci
510e41f4b71Sopenharmony_ci@Entry
511e41f4b71Sopenharmony_ci@ComponentV2
512e41f4b71Sopenharmony_ciexport struct RepeatTemplateSingle {
513e41f4b71Sopenharmony_ci  @Local arrayHolder: ArrayHolder = new ArrayHolder(100);
514e41f4b71Sopenharmony_ci  @Local totalCount: number = this.arrayHolder.arr.length;
515e41f4b71Sopenharmony_ci  scroller: Scroller = new Scroller();
516e41f4b71Sopenharmony_ci
517e41f4b71Sopenharmony_ci  build() {
518e41f4b71Sopenharmony_ci    Column({ space: 5 }) {
519e41f4b71Sopenharmony_ci      List({ space: 20, initialIndex: 19, scroller: this.scroller }) {
520e41f4b71Sopenharmony_ci        Repeat(this.arrayHolder.arr)
521e41f4b71Sopenharmony_ci          .virtualScroll({ totalCount: this.totalCount })
522e41f4b71Sopenharmony_ci          .templateId((item, index) => {
523e41f4b71Sopenharmony_ci            return 'number';
524e41f4b71Sopenharmony_ci          })
525e41f4b71Sopenharmony_ci          .template('number', (r) => {
526e41f4b71Sopenharmony_ci            ListItem() {
527e41f4b71Sopenharmony_ci              Text(r.index! + ":" + r.item + "Reuse");
528e41f4b71Sopenharmony_ci            }
529e41f4b71Sopenharmony_ci          })
530e41f4b71Sopenharmony_ci          .each((r) => {
531e41f4b71Sopenharmony_ci            ListItem() {
532e41f4b71Sopenharmony_ci              Text(r.index! + ":" + r.item + "eachMessage");
533e41f4b71Sopenharmony_ci            }
534e41f4b71Sopenharmony_ci          })
535e41f4b71Sopenharmony_ci      }
536e41f4b71Sopenharmony_ci      .height('30%')
537e41f4b71Sopenharmony_ci
538e41f4b71Sopenharmony_ci      Button(`insert totalCount ${this.totalCount}`)
539e41f4b71Sopenharmony_ci        .height(60)
540e41f4b71Sopenharmony_ci        .onClick(() => {
541e41f4b71Sopenharmony_ci          // 插入元素,元素位置为屏幕显示的前一个元素
542e41f4b71Sopenharmony_ci          this.arrayHolder.arr.splice(18, 0, this.totalCount);
543e41f4b71Sopenharmony_ci          this.totalCount = this.arrayHolder.arr.length;
544e41f4b71Sopenharmony_ci        })
545e41f4b71Sopenharmony_ci    }
546e41f4b71Sopenharmony_ci    .width('100%')
547e41f4b71Sopenharmony_ci    .margin({ top: 5 })
548e41f4b71Sopenharmony_ci  }
549e41f4b71Sopenharmony_ci}
550e41f4b71Sopenharmony_ci```
551e41f4b71Sopenharmony_ci
552e41f4b71Sopenharmony_ci运行效果:
553e41f4b71Sopenharmony_ci
554e41f4b71Sopenharmony_ci![Repeat-case1-Error](./figures/Repeat-Case1-Error.gif)
555e41f4b71Sopenharmony_ci
556e41f4b71Sopenharmony_ci在一些场景中,我们不希望屏幕外的数据源变化影响屏幕中List列表Scroller停留的位置,可以通过List组件的[onScrollIndex](../ui/arkts-layout-development-create-list.md#响应滚动位置)事件对列表滚动动作进行监听,当列表发生滚动时,获取列表滚动位置。使用Scroller组件的[scrollToIndex](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scrolltoindex)特性,滑动到指定index位置,实现屏幕外的数据源增加/删除数据时,Scroller停留的位置不变的效果。
557e41f4b71Sopenharmony_ci
558e41f4b71Sopenharmony_ci示例代码仅对增加数据的情况进行展示。
559e41f4b71Sopenharmony_ci
560e41f4b71Sopenharmony_ci```ts
561e41f4b71Sopenharmony_ci// 定义一个类,标记为可观察的
562e41f4b71Sopenharmony_ci// 类中自定义一个数组,标记为可追踪的
563e41f4b71Sopenharmony_ci@ObservedV2
564e41f4b71Sopenharmony_ciclass ArrayHolder {
565e41f4b71Sopenharmony_ci  @Trace arr: Array<number> = [];
566e41f4b71Sopenharmony_ci
567e41f4b71Sopenharmony_ci  // constructor,用于初始化数组个数
568e41f4b71Sopenharmony_ci  constructor(count: number) {
569e41f4b71Sopenharmony_ci    for (let i = 0; i < count; i++) {
570e41f4b71Sopenharmony_ci      this.arr.push(i);
571e41f4b71Sopenharmony_ci    }
572e41f4b71Sopenharmony_ci  }
573e41f4b71Sopenharmony_ci}
574e41f4b71Sopenharmony_ci
575e41f4b71Sopenharmony_ci@Entry
576e41f4b71Sopenharmony_ci@ComponentV2
577e41f4b71Sopenharmony_ciexport struct RepeatTemplateSingle {
578e41f4b71Sopenharmony_ci  @Local arrayHolder: ArrayHolder = new ArrayHolder(100);
579e41f4b71Sopenharmony_ci  @Local totalCount: number = this.arrayHolder.arr.length;
580e41f4b71Sopenharmony_ci  scroller: Scroller = new Scroller();
581e41f4b71Sopenharmony_ci
582e41f4b71Sopenharmony_ci  private start: number = 1;
583e41f4b71Sopenharmony_ci  private end: number = 1;
584e41f4b71Sopenharmony_ci
585e41f4b71Sopenharmony_ci  build() {
586e41f4b71Sopenharmony_ci    Column({ space: 5 }) {
587e41f4b71Sopenharmony_ci      List({ space: 20, initialIndex: 19, scroller: this.scroller }) {
588e41f4b71Sopenharmony_ci        Repeat(this.arrayHolder.arr)
589e41f4b71Sopenharmony_ci          .virtualScroll({ totalCount: this.totalCount })
590e41f4b71Sopenharmony_ci          .templateId((item, index) => {
591e41f4b71Sopenharmony_ci            return 'number';
592e41f4b71Sopenharmony_ci          })
593e41f4b71Sopenharmony_ci          .template('number', (r) => {
594e41f4b71Sopenharmony_ci            ListItem() {
595e41f4b71Sopenharmony_ci              Text(r.index! + ":" + r.item + "Reuse");
596e41f4b71Sopenharmony_ci            }
597e41f4b71Sopenharmony_ci          })
598e41f4b71Sopenharmony_ci          .each((r) => {
599e41f4b71Sopenharmony_ci            ListItem() {
600e41f4b71Sopenharmony_ci              Text(r.index! + ":" + r.item + "eachMessage");
601e41f4b71Sopenharmony_ci            }
602e41f4b71Sopenharmony_ci          })
603e41f4b71Sopenharmony_ci      }
604e41f4b71Sopenharmony_ci      .onScrollIndex((start, end) => {
605e41f4b71Sopenharmony_ci        this.start = start;
606e41f4b71Sopenharmony_ci        this.end = end;
607e41f4b71Sopenharmony_ci      })
608e41f4b71Sopenharmony_ci      .height('30%')
609e41f4b71Sopenharmony_ci
610e41f4b71Sopenharmony_ci      Button(`insert totalCount ${this.totalCount}`)
611e41f4b71Sopenharmony_ci        .height(60)
612e41f4b71Sopenharmony_ci        .onClick(() => {
613e41f4b71Sopenharmony_ci          // 插入元素,元素位置为屏幕显示的前一个元素
614e41f4b71Sopenharmony_ci          this.arrayHolder.arr.splice(18, 0, this.totalCount);
615e41f4b71Sopenharmony_ci          let rect = this.scroller.getItemRect(this.start); // 获取子组件的大小位置
616e41f4b71Sopenharmony_ci          this.scroller.scrollToIndex(this.start + 1); // 滑动到指定index
617e41f4b71Sopenharmony_ci          this.scroller.scrollBy(0, -rect.y); // 滑动指定距离
618e41f4b71Sopenharmony_ci          this.totalCount = this.arrayHolder.arr.length;
619e41f4b71Sopenharmony_ci        })
620e41f4b71Sopenharmony_ci    }
621e41f4b71Sopenharmony_ci    .width('100%')
622e41f4b71Sopenharmony_ci    .margin({ top: 5 })
623e41f4b71Sopenharmony_ci  }
624e41f4b71Sopenharmony_ci}
625e41f4b71Sopenharmony_ci```
626e41f4b71Sopenharmony_ci
627e41f4b71Sopenharmony_ci运行效果:
628e41f4b71Sopenharmony_ci
629e41f4b71Sopenharmony_ci![Repeat-case1-Succ](./figures/Repeat-Case1-Succ.gif)
630e41f4b71Sopenharmony_ci
631e41f4b71Sopenharmony_ci### totalCount值大于数据源长度
632e41f4b71Sopenharmony_ci
633e41f4b71Sopenharmony_ci当数据源总长度很大时,会使用懒加载的方式先加载一部分数据,为了使Repeat显示正确的滚动条样式,需要将数据总长度赋值给totalCount,即数据源全部加载完成前,totalCount大于array.length634e41f4b71Sopenharmony_ci
635e41f4b71Sopenharmony_citotalCount > array.length时,在父组件容器滚动过程中,应用需要保证列表即将滑动到数据源末尾时请求后续数据,开发者需要对数据请求的错误场景(如网络延迟)进行保护操作,直到数据源全部加载完成,否则列表滑动的过程中会出现滚动效果异常。
636e41f4b71Sopenharmony_ci
637e41f4b71Sopenharmony_ci上述规范可以通过实现父组件List/Grid的[onScrollIndex](../ui/arkts-layout-development-create-list.md#响应滚动位置)属性的回调函数完成。示例代码如下:
638e41f4b71Sopenharmony_ci
639e41f4b71Sopenharmony_ci```ts
640e41f4b71Sopenharmony_ci@ObservedV2
641e41f4b71Sopenharmony_ciclass VehicleData {
642e41f4b71Sopenharmony_ci  @Trace name: string;
643e41f4b71Sopenharmony_ci  @Trace price: number;
644e41f4b71Sopenharmony_ci
645e41f4b71Sopenharmony_ci  constructor(name: string, price: number) {
646e41f4b71Sopenharmony_ci    this.name = name;
647e41f4b71Sopenharmony_ci    this.price = price;
648e41f4b71Sopenharmony_ci  }
649e41f4b71Sopenharmony_ci}
650e41f4b71Sopenharmony_ci
651e41f4b71Sopenharmony_ci@ObservedV2
652e41f4b71Sopenharmony_ciclass VehicleDB {
653e41f4b71Sopenharmony_ci  public vehicleItems: VehicleData[] = [];
654e41f4b71Sopenharmony_ci
655e41f4b71Sopenharmony_ci  constructor() {
656e41f4b71Sopenharmony_ci    // init data size 20
657e41f4b71Sopenharmony_ci    for (let i = 1; i <= 20; i++) {
658e41f4b71Sopenharmony_ci      this.vehicleItems.push(new VehicleData(`Vehicle${i}`, i));
659e41f4b71Sopenharmony_ci    }
660e41f4b71Sopenharmony_ci  }
661e41f4b71Sopenharmony_ci}
662e41f4b71Sopenharmony_ci
663e41f4b71Sopenharmony_ci@Entry
664e41f4b71Sopenharmony_ci@ComponentV2
665e41f4b71Sopenharmony_cistruct entryCompSucc {
666e41f4b71Sopenharmony_ci  @Local vehicleItems: VehicleData[] = new VehicleDB().vehicleItems;
667e41f4b71Sopenharmony_ci  @Local listChildrenSize: ChildrenMainSize = new ChildrenMainSize(60);
668e41f4b71Sopenharmony_ci  @Local totalCount: number = this.vehicleItems.length;
669e41f4b71Sopenharmony_ci  scroller: Scroller = new Scroller();
670e41f4b71Sopenharmony_ci
671e41f4b71Sopenharmony_ci  build() {
672e41f4b71Sopenharmony_ci    Column({ space: 3 }) {
673e41f4b71Sopenharmony_ci      List({ scroller: this.scroller }) {
674e41f4b71Sopenharmony_ci        Repeat(this.vehicleItems)
675e41f4b71Sopenharmony_ci          .virtualScroll({ totalCount: 50 }) // total data size 50
676e41f4b71Sopenharmony_ci          .templateId(() => 'default')
677e41f4b71Sopenharmony_ci          .template('default', (ri) => {
678e41f4b71Sopenharmony_ci            ListItem() {
679e41f4b71Sopenharmony_ci              Column() {
680e41f4b71Sopenharmony_ci                Text(`${ri.item.name} + ${ri.index}`)
681e41f4b71Sopenharmony_ci                  .width('90%')
682e41f4b71Sopenharmony_ci                  .height(this.listChildrenSize.childDefaultSize)
683e41f4b71Sopenharmony_ci                  .backgroundColor(0xFFA07A)
684e41f4b71Sopenharmony_ci                  .textAlign(TextAlign.Center)
685e41f4b71Sopenharmony_ci                  .fontSize(20)
686e41f4b71Sopenharmony_ci                  .fontWeight(FontWeight.Bold)
687e41f4b71Sopenharmony_ci              }
688e41f4b71Sopenharmony_ci            }.border({ width: 1 })
689e41f4b71Sopenharmony_ci          }, { cachedCount: 5 })
690e41f4b71Sopenharmony_ci          .each((ri) => {
691e41f4b71Sopenharmony_ci            ListItem() {
692e41f4b71Sopenharmony_ci              Text("Wrong: " + `${ri.item.name} + ${ri.index}`)
693e41f4b71Sopenharmony_ci                .width('90%')
694e41f4b71Sopenharmony_ci                .height(this.listChildrenSize.childDefaultSize)
695e41f4b71Sopenharmony_ci                .backgroundColor(0xFFA07A)
696e41f4b71Sopenharmony_ci                .textAlign(TextAlign.Center)
697e41f4b71Sopenharmony_ci                .fontSize(20)
698e41f4b71Sopenharmony_ci                .fontWeight(FontWeight.Bold)
699e41f4b71Sopenharmony_ci            }.border({ width: 1 })
700e41f4b71Sopenharmony_ci          })
701e41f4b71Sopenharmony_ci          .key((item, index) => `${index}:${item}`)
702e41f4b71Sopenharmony_ci      }
703e41f4b71Sopenharmony_ci      .height('50%')
704e41f4b71Sopenharmony_ci      .margin({ top: 20 })
705e41f4b71Sopenharmony_ci      .childrenMainSize(this.listChildrenSize)
706e41f4b71Sopenharmony_ci      .alignListItem(ListItemAlign.Center)
707e41f4b71Sopenharmony_ci      .onScrollIndex((start, end) => {
708e41f4b71Sopenharmony_ci        console.log('onScrollIndex', start, end);
709e41f4b71Sopenharmony_ci        // lazy data loading
710e41f4b71Sopenharmony_ci        if (this.vehicleItems.length < 50) {
711e41f4b71Sopenharmony_ci          for (let i = 0; i < 10; i++) {
712e41f4b71Sopenharmony_ci            if (this.vehicleItems.length < 50) {
713e41f4b71Sopenharmony_ci              this.vehicleItems.push(new VehicleData("Vehicle_loaded", i));
714e41f4b71Sopenharmony_ci            }
715e41f4b71Sopenharmony_ci          }
716e41f4b71Sopenharmony_ci        }
717e41f4b71Sopenharmony_ci      })
718e41f4b71Sopenharmony_ci    }
719e41f4b71Sopenharmony_ci  }
720e41f4b71Sopenharmony_ci}
721e41f4b71Sopenharmony_ci```
722e41f4b71Sopenharmony_ci
723e41f4b71Sopenharmony_ci示例代码运行效果:
724e41f4b71Sopenharmony_ci
725e41f4b71Sopenharmony_ci![Repeat-Case2-Succ](./figures/Repeat-Case2-Succ.gif)