1# LazyForEach:数据懒加载
2
3API参数说明见:[LazyForEach API参数说明](../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md)
4
5LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
6
7## 使用限制
8
9- LazyForEach必须在容器组件内使用,仅有[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)组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
10- 容器组件內使用LazyForEach的时候,只能包含一个LazyForEach。以List为例,同时包含ListItem、ForEach、LazyForEach的情形是不推荐的;同时包含多个LazyForEach也是不推荐的。
11- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件。
12- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
13- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
14- 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
15- LazyForEach必须使用DataChangeListener对象进行更新,对第一个参数dataSource重新赋值会异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。
16- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。
17- LazyForEach必须和[@Reusable](https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-component-reuse-V5#section5601835174020)装饰器一起使用才能触发节点复用。使用方法:将@Reusable装饰在LazyForEach列表的组件上,见[使用规则](https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-component-reuse-V5#section5923195311402)。
18
19## 键值生成规则
20
21在`LazyForEach`循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
22
23`LazyForEach`提供了一个名为`keyGenerator`的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义`keyGenerator`函数,则ArkUI框架会使用默认的键值生成函数,即`(item: Object, index: number) => { return viewId + '-' + index.toString(); }`, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。
24
25## 组件创建规则
26
27在确定键值生成规则后,LazyForEach的第二个参数`itemGenerator`函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:[LazyForEach首次渲染](#首次渲染)和[LazyForEach非首次渲染](#非首次渲染)。
28
29### 首次渲染
30
31#### 生成不同键值
32
33在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
34
35```ts
36// Basic implementation of IDataSource to handle data listener
37class BasicDataSource implements IDataSource {
38  private listeners: DataChangeListener[] = [];
39  private originDataArray: string[] = [];
40
41  public totalCount(): number {
42    return 0;
43  }
44
45  public getData(index: number): string {
46    return this.originDataArray[index];
47  }
48
49  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
50  registerDataChangeListener(listener: DataChangeListener): void {
51    if (this.listeners.indexOf(listener) < 0) {
52      console.info('add listener');
53      this.listeners.push(listener);
54    }
55  }
56
57  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
58  unregisterDataChangeListener(listener: DataChangeListener): void {
59    const pos = this.listeners.indexOf(listener);
60    if (pos >= 0) {
61      console.info('remove listener');
62      this.listeners.splice(pos, 1);
63    }
64  }
65
66  // 通知LazyForEach组件需要重载所有子组件
67  notifyDataReload(): void {
68    this.listeners.forEach(listener => {
69      listener.onDataReloaded();
70    })
71  }
72
73  // 通知LazyForEach组件需要在index对应索引处添加子组件
74  notifyDataAdd(index: number): void {
75    this.listeners.forEach(listener => {
76      listener.onDataAdd(index);
77    })
78  }
79
80  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
81  notifyDataChange(index: number): void {
82    this.listeners.forEach(listener => {
83      listener.onDataChange(index);
84    })
85  }
86
87  // 通知LazyForEach组件需要在index对应索引处删除该子组件
88  notifyDataDelete(index: number): void {
89    this.listeners.forEach(listener => {
90      listener.onDataDelete(index);
91    })
92  }
93
94  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
95  notifyDataMove(from: number, to: number): void {
96    this.listeners.forEach(listener => {
97      listener.onDataMove(from, to);
98    })
99  }
100}
101
102class MyDataSource extends BasicDataSource {
103  private dataArray: string[] = [];
104
105  public totalCount(): number {
106    return this.dataArray.length;
107  }
108
109  public getData(index: number): string {
110    return this.dataArray[index];
111  }
112
113  public addData(index: number, data: string): void {
114    this.dataArray.splice(index, 0, data);
115    this.notifyDataAdd(index);
116  }
117
118  public pushData(data: string): void {
119    this.dataArray.push(data);
120    this.notifyDataAdd(this.dataArray.length - 1);
121  }
122}
123
124@Entry
125@Component
126struct MyComponent {
127  private data: MyDataSource = new MyDataSource();
128   
129  aboutToAppear() {
130    for (let i = 0; i <= 20; i++) {
131      this.data.pushData(`Hello ${i}`)
132    }
133  }
134
135  build() {
136    List({ space: 3 }) {
137      LazyForEach(this.data, (item: string) => {
138        ListItem() {
139          Row() {
140            Text(item).fontSize(50)
141              .onAppear(() => {
142                console.info("appear:" + item)
143              })
144          }.margin({ left: 10, right: 10 })
145        }
146      }, (item: string) => item)
147    }.cachedCount(5)
148  }
149}
150```
151
152在上述代码中,键值生成规则是`keyGenerator`函数的返回值`item`。在`LazyForEach`循环渲染时,其为数据源数组项依次生成键值`Hello 0`、`Hello 1` ... `Hello 20`,并创建对应的`ListItem`子组件渲染到界面上。
153
154运行效果如下图所示。
155
156**图1**  LazyForEach正常首次渲染  
157![LazyForEach-Render-DifferentKey](./figures/LazyForEach-Render-DifferentKey.gif)
158
159#### 键值相同时错误渲染
160
161当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,`LazyForEach`渲染的数据项键值均相同,在滑动过程中,`LazyForEach`会对划入划出当前页面的子组件进行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架可能存在取用缓存错误的情况,导致子组件渲染有问题。
162
163 ```ts
164class BasicDataSource implements IDataSource {
165  private listeners: DataChangeListener[] = [];
166  private originDataArray: string[] = [];
167
168  public totalCount(): number {
169    return 0;
170  }
171
172  public getData(index: number): string {
173    return this.originDataArray[index];
174  }
175
176  registerDataChangeListener(listener: DataChangeListener): void {
177    if (this.listeners.indexOf(listener) < 0) {
178      console.info('add listener');
179      this.listeners.push(listener);
180    }
181  }
182
183  unregisterDataChangeListener(listener: DataChangeListener): void {
184    const pos = this.listeners.indexOf(listener);
185    if (pos >= 0) {
186      console.info('remove listener');
187      this.listeners.splice(pos, 1);
188    }
189  }
190
191  notifyDataReload(): void {
192    this.listeners.forEach(listener => {
193      listener.onDataReloaded();
194    })
195  }
196
197  notifyDataAdd(index: number): void {
198    this.listeners.forEach(listener => {
199      listener.onDataAdd(index);
200    })
201  }
202
203  notifyDataChange(index: number): void {
204    this.listeners.forEach(listener => {
205      listener.onDataChange(index);
206    })
207  }
208
209  notifyDataDelete(index: number): void {
210    this.listeners.forEach(listener => {
211      listener.onDataDelete(index);
212    })
213  }
214
215  notifyDataMove(from: number, to: number): void {
216    this.listeners.forEach(listener => {
217      listener.onDataMove(from, to);
218    })
219  }
220}
221
222class MyDataSource extends BasicDataSource {
223  private dataArray: string[] = [];
224
225  public totalCount(): number {
226    return this.dataArray.length;
227  }
228
229  public getData(index: number): string {
230    return this.dataArray[index];
231  }
232
233  public addData(index: number, data: string): void {
234    this.dataArray.splice(index, 0, data);
235    this.notifyDataAdd(index);
236  }
237
238  public pushData(data: string): void {
239    this.dataArray.push(data);
240    this.notifyDataAdd(this.dataArray.length - 1);
241  }
242}
243
244@Entry
245@Component
246struct MyComponent {
247  private data: MyDataSource = new MyDataSource();
248
249  aboutToAppear() {
250    for (let i = 0; i <= 20; i++) {
251      this.data.pushData(`Hello ${i}`)
252    }
253  }
254
255  build() {
256    List({ space: 3 }) {
257      LazyForEach(this.data, (item: string) => {
258        ListItem() {
259          Row() {
260            Text(item).fontSize(50)
261              .onAppear(() => {
262                console.info("appear:" + item)
263              })
264          }.margin({ left: 10, right: 10 })
265        }
266      }, (item: string) => 'same key')
267    }.cachedCount(5)
268  }
269}
270 ```
271
272运行效果如下图所示。
273
274**图2**  LazyForEach存在相同键值  
275![LazyForEach-Render-SameKey](./figures/LazyForEach-Render-SameKey.gif)
276
277### 非首次渲染
278
279当`LazyForEach`数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用`listener`对应的接口,通知`LazyForEach`做相应的更新,各使用场景如下。
280
281#### 添加数据
282
283```ts
284class BasicDataSource implements IDataSource {
285  private listeners: DataChangeListener[] = [];
286  private originDataArray: string[] = [];
287
288  public totalCount(): number {
289    return 0;
290  }
291
292  public getData(index: number): string {
293    return this.originDataArray[index];
294  }
295
296  registerDataChangeListener(listener: DataChangeListener): void {
297    if (this.listeners.indexOf(listener) < 0) {
298      console.info('add listener');
299      this.listeners.push(listener);
300    }
301  }
302
303  unregisterDataChangeListener(listener: DataChangeListener): void {
304    const pos = this.listeners.indexOf(listener);
305    if (pos >= 0) {
306      console.info('remove listener');
307      this.listeners.splice(pos, 1);
308    }
309  }
310
311  notifyDataReload(): void {
312    this.listeners.forEach(listener => {
313      listener.onDataReloaded();
314    })
315  }
316
317  notifyDataAdd(index: number): void {
318    this.listeners.forEach(listener => {
319      listener.onDataAdd(index);
320      // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);
321    })
322  }
323
324  notifyDataChange(index: number): void {
325    this.listeners.forEach(listener => {
326      listener.onDataChange(index);
327    })
328  }
329
330  notifyDataDelete(index: number): void {
331    this.listeners.forEach(listener => {
332      listener.onDataDelete(index);
333    })
334  }
335
336  notifyDataMove(from: number, to: number): void {
337    this.listeners.forEach(listener => {
338      listener.onDataMove(from, to);
339    })
340  }
341}
342
343class MyDataSource extends BasicDataSource {
344  private dataArray: string[] = [];
345
346  public totalCount(): number {
347    return this.dataArray.length;
348  }
349
350  public getData(index: number): string {
351    return this.dataArray[index];
352  }
353
354  public addData(index: number, data: string): void {
355    this.dataArray.splice(index, 0, data);
356    this.notifyDataAdd(index);
357  }
358
359  public pushData(data: string): void {
360    this.dataArray.push(data);
361    this.notifyDataAdd(this.dataArray.length - 1);
362  }
363}
364
365@Entry
366@Component
367struct MyComponent {
368  private data: MyDataSource = new MyDataSource();
369
370  aboutToAppear() {
371    for (let i = 0; i <= 20; i++) {
372      this.data.pushData(`Hello ${i}`)
373    }
374  }
375
376  build() {
377    List({ space: 3 }) {
378      LazyForEach(this.data, (item: string) => {
379        ListItem() {
380          Row() {
381            Text(item).fontSize(50)
382              .onAppear(() => {
383                console.info("appear:" + item)
384              })
385          }.margin({ left: 10, right: 10 })
386        }
387        .onClick(() => {
388          // 点击追加子组件
389          this.data.pushData(`Hello ${this.data.totalCount()}`);
390        })
391      }, (item: string) => item)
392    }.cachedCount(5)
393  }
394}
395```
396
397当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`pushData`方法,该方法会在数据源末尾添加数据并调用`notifyDataAdd`方法。在`notifyDataAdd`方法内会又调用`listener.onDataAdd`方法,该方法会通知`LazyForEach`在该处有数据添加,`LazyForEach`便会在该索引处新建子组件。
398
399运行效果如下图所示。
400
401**图3**  LazyForEach添加数据  
402![LazyForEach-Add-Data](./figures/LazyForEach-Add-Data.gif)
403
404#### 删除数据
405
406```ts
407class BasicDataSource implements IDataSource {
408  private listeners: DataChangeListener[] = [];
409  private originDataArray: string[] = [];
410
411  public totalCount(): number {
412    return 0;
413  }
414
415  public getData(index: number): string {
416    return this.originDataArray[index];
417  }
418
419  registerDataChangeListener(listener: DataChangeListener): void {
420    if (this.listeners.indexOf(listener) < 0) {
421      console.info('add listener');
422      this.listeners.push(listener);
423    }
424  }
425
426  unregisterDataChangeListener(listener: DataChangeListener): void {
427    const pos = this.listeners.indexOf(listener);
428    if (pos >= 0) {
429      console.info('remove listener');
430      this.listeners.splice(pos, 1);
431    }
432  }
433
434  notifyDataReload(): void {
435    this.listeners.forEach(listener => {
436      listener.onDataReloaded();
437    })
438  }
439
440  notifyDataAdd(index: number): void {
441    this.listeners.forEach(listener => {
442      listener.onDataAdd(index);
443    })
444  }
445
446  notifyDataChange(index: number): void {
447    this.listeners.forEach(listener => {
448      listener.onDataChange(index);
449    })
450  }
451
452  notifyDataDelete(index: number): void {
453    this.listeners.forEach(listener => {
454      listener.onDataDelete(index);
455      // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);
456    })
457  }
458
459  notifyDataMove(from: number, to: number): void {
460    this.listeners.forEach(listener => {
461      listener.onDataMove(from, to);
462    })
463  }
464}
465
466class MyDataSource extends BasicDataSource {
467  dataArray: string[] = [];
468
469  public totalCount(): number {
470    return this.dataArray.length;
471  }
472
473  public getData(index: number): string {
474    return this.dataArray[index];
475  }
476
477  public addData(index: number, data: string): void {
478    this.dataArray.splice(index, 0, data);
479    this.notifyDataAdd(index);
480  }
481
482  public pushData(data: string): void {
483    this.dataArray.push(data);
484  }
485  
486  public deleteData(index: number): void {
487    this.dataArray.splice(index, 1);
488    this.notifyDataDelete(index);
489  }
490}
491
492@Entry
493@Component
494struct MyComponent {
495  private data: MyDataSource = new MyDataSource();
496
497  aboutToAppear() {
498    for (let i = 0; i <= 20; i++) {
499      this.data.pushData(`Hello ${i}`)
500    }
501  }
502
503  build() {
504    List({ space: 3 }) {
505      LazyForEach(this.data, (item: string, index: number) => {
506        ListItem() {
507          Row() {
508            Text(item).fontSize(50)
509              .onAppear(() => {
510                console.info("appear:" + item)
511              })
512          }.margin({ left: 10, right: 10 })
513        }
514        .onClick(() => {
515          // 点击删除子组件
516          this.data.deleteData(this.data.dataArray.indexOf(item));
517        })
518      }, (item: string) => item)
519    }.cachedCount(5)
520  }
521}
522```
523
524当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`deleteData`方法,该方法会删除数据源对应索引处的数据并调用`notifyDataDelete`方法。在`notifyDataDelete`方法内会又调用`listener.onDataDelete`方法,该方法会通知`LazyForEach`在该处有数据删除,`LazyForEach`便会在该索引处删除对应子组件。
525
526运行效果如下图所示。
527
528**图4**  LazyForEach删除数据  
529![LazyForEach-Delete-Data](./figures/LazyForEach-Delete-Data.gif)
530
531#### 交换数据
532
533```ts
534class BasicDataSource implements IDataSource {
535  private listeners: DataChangeListener[] = [];
536  private originDataArray: string[] = [];
537
538  public totalCount(): number {
539    return 0;
540  }
541
542  public getData(index: number): string {
543    return this.originDataArray[index];
544  }
545
546  registerDataChangeListener(listener: DataChangeListener): void {
547    if (this.listeners.indexOf(listener) < 0) {
548      console.info('add listener');
549      this.listeners.push(listener);
550    }
551  }
552
553  unregisterDataChangeListener(listener: DataChangeListener): void {
554    const pos = this.listeners.indexOf(listener);
555    if (pos >= 0) {
556      console.info('remove listener');
557      this.listeners.splice(pos, 1);
558    }
559  }
560
561  notifyDataReload(): void {
562    this.listeners.forEach(listener => {
563      listener.onDataReloaded();
564    })
565  }
566
567  notifyDataAdd(index: number): void {
568    this.listeners.forEach(listener => {
569      listener.onDataAdd(index);
570    })
571  }
572
573  notifyDataChange(index: number): void {
574    this.listeners.forEach(listener => {
575      listener.onDataChange(index);
576    })
577  }
578
579  notifyDataDelete(index: number): void {
580    this.listeners.forEach(listener => {
581      listener.onDataDelete(index);
582    })
583  }
584
585  notifyDataMove(from: number, to: number): void {
586    this.listeners.forEach(listener => {
587      listener.onDataMove(from, to);
588      // 写法2:listener.onDatasetChange(
589      //         [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]);
590    })
591  }
592}
593
594class MyDataSource extends BasicDataSource {
595  dataArray: string[] = [];
596
597  public totalCount(): number {
598    return this.dataArray.length;
599  }
600
601  public getData(index: number): string {
602    return this.dataArray[index];
603  }
604
605  public addData(index: number, data: string): void {
606    this.dataArray.splice(index, 0, data);
607    this.notifyDataAdd(index);
608  }
609
610  public pushData(data: string): void {
611    this.dataArray.push(data);
612  }
613  
614  public deleteData(index: number): void {
615    this.dataArray.splice(index, 1);
616    this.notifyDataDelete(index);
617  }
618  
619  public moveData(from: number, to: number): void {
620    let temp: string = this.dataArray[from];
621    this.dataArray[from] = this.dataArray[to];
622    this.dataArray[to] = temp;
623    this.notifyDataMove(from, to);
624  }
625}
626
627@Entry
628@Component
629struct MyComponent {
630  private moved: number[] = [];
631  private data: MyDataSource = new MyDataSource();
632
633  aboutToAppear() {
634    for (let i = 0; i <= 20; i++) {
635      this.data.pushData(`Hello ${i}`)
636    }
637  }
638
639  build() {
640    List({ space: 3 }) {
641      LazyForEach(this.data, (item: string, index: number) => {
642        ListItem() {
643          Row() {
644            Text(item).fontSize(50)
645              .onAppear(() => {
646                console.info("appear:" + item)
647              })
648          }.margin({ left: 10, right: 10 })
649        }
650        .onClick(() => {
651          this.moved.push(this.data.dataArray.indexOf(item));
652          if (this.moved.length === 2) {
653          	// 点击交换子组件
654          	this.data.moveData(this.moved[0], this.moved[1]);
655            this.moved = [];
656          }
657        })
658      }, (item: string) => item)
659    }.cachedCount(5)
660  }
661}
662```
663
664当我们首次点击`LazyForEach`的子组件时,在moved成员变量内存入要移动的数据索引,再次点击`LazyForEach`另一个子组件时,我们将首次点击的子组件移到此处。调用数据源`data`的`moveData`方法,该方法会将数据源对应数据移动到预期的位置并调用`notifyDataMove`方法。在`notifyDataMove`方法内会又调用`listener.onDataMove`方法,该方法通知`LazyForEach`在该处有数据需要移动,`LazyForEach`便会将`from`和`to`索引处的子组件进行位置调换。
665
666运行效果如下图所示。
667
668**图5**  LazyForEach交换数据  
669![LazyForEach-Exchange-Data](./figures/LazyForEach-Exchange-Data.gif)
670
671#### 改变单个数据
672
673```ts
674class BasicDataSource implements IDataSource {
675  private listeners: DataChangeListener[] = [];
676  private originDataArray: string[] = [];
677
678  public totalCount(): number {
679    return 0;
680  }
681
682  public getData(index: number): string {
683    return this.originDataArray[index];
684  }
685
686  registerDataChangeListener(listener: DataChangeListener): void {
687    if (this.listeners.indexOf(listener) < 0) {
688      console.info('add listener');
689      this.listeners.push(listener);
690    }
691  }
692
693  unregisterDataChangeListener(listener: DataChangeListener): void {
694    const pos = this.listeners.indexOf(listener);
695    if (pos >= 0) {
696      console.info('remove listener');
697      this.listeners.splice(pos, 1);
698    }
699  }
700
701  notifyDataReload(): void {
702    this.listeners.forEach(listener => {
703      listener.onDataReloaded();
704    })
705  }
706
707  notifyDataAdd(index: number): void {
708    this.listeners.forEach(listener => {
709      listener.onDataAdd(index);
710    })
711  }
712
713  notifyDataChange(index: number): void {
714    this.listeners.forEach(listener => {
715      listener.onDataChange(index);
716      // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]);
717    })
718  }
719
720  notifyDataDelete(index: number): void {
721    this.listeners.forEach(listener => {
722      listener.onDataDelete(index);
723    })
724  }
725
726  notifyDataMove(from: number, to: number): void {
727    this.listeners.forEach(listener => {
728      listener.onDataMove(from, to);
729    })
730  }
731}
732
733class MyDataSource extends BasicDataSource {
734  private dataArray: string[] = [];
735
736  public totalCount(): number {
737    return this.dataArray.length;
738  }
739
740  public getData(index: number): string {
741    return this.dataArray[index];
742  }
743
744  public addData(index: number, data: string): void {
745    this.dataArray.splice(index, 0, data);
746    this.notifyDataAdd(index);
747  }
748
749  public pushData(data: string): void {
750    this.dataArray.push(data);
751  }
752  
753  public deleteData(index: number): void {
754    this.dataArray.splice(index, 1);
755    this.notifyDataDelete(index);
756  }
757  
758  public changeData(index: number, data: string): void {
759    this.dataArray.splice(index, 1, data);
760    this.notifyDataChange(index);
761  }
762}
763
764@Entry
765@Component
766struct MyComponent {
767  private moved: number[] = [];
768  private data: MyDataSource = new MyDataSource();
769
770  aboutToAppear() {
771    for (let i = 0; i <= 20; i++) {
772      this.data.pushData(`Hello ${i}`)
773    }
774  }
775
776
777  build() {
778    List({ space: 3 }) {
779      LazyForEach(this.data, (item: string, index: number) => {
780        ListItem() {
781          Row() {
782            Text(item).fontSize(50)
783              .onAppear(() => {
784                console.info("appear:" + item)
785              })
786          }.margin({ left: 10, right: 10 })
787        }
788        .onClick(() => {
789          this.data.changeData(index, item + '00');
790        })
791      }, (item: string) => item)
792    }.cachedCount(5)
793  }
794}
795```
796
797当我们点击`LazyForEach`的子组件时,首先改变当前数据,然后调用数据源`data`的`changeData`方法,在该方法内会调用`notifyDataChange`方法。在`notifyDataChange`方法内会又调用`listener.onDataChange`方法,该方法通知`LazyForEach`组件该处有数据发生变化,`LazyForEach`便会在对应索引处重建子组件。
798
799运行效果如下图所示。
800
801**图6**  LazyForEach改变单个数据  
802![LazyForEach-Change-SingleData](./figures/LazyForEach-Change-SingleData.gif)
803
804#### 改变多个数据
805
806```ts
807class BasicDataSource implements IDataSource {
808  private listeners: DataChangeListener[] = [];
809  private originDataArray: string[] = [];
810
811  public totalCount(): number {
812    return 0;
813  }
814
815  public getData(index: number): string {
816    return this.originDataArray[index];
817  }
818
819  registerDataChangeListener(listener: DataChangeListener): void {
820    if (this.listeners.indexOf(listener) < 0) {
821      console.info('add listener');
822      this.listeners.push(listener);
823    }
824  }
825
826  unregisterDataChangeListener(listener: DataChangeListener): void {
827    const pos = this.listeners.indexOf(listener);
828    if (pos >= 0) {
829      console.info('remove listener');
830      this.listeners.splice(pos, 1);
831    }
832  }
833
834  notifyDataReload(): void {
835    this.listeners.forEach(listener => {
836      listener.onDataReloaded();
837      // 写法2:listener.onDatasetChange([{type: DataOperationType.RELOAD}]);
838    })
839  }
840
841  notifyDataAdd(index: number): void {
842    this.listeners.forEach(listener => {
843      listener.onDataAdd(index);
844    })
845  }
846
847  notifyDataChange(index: number): void {
848    this.listeners.forEach(listener => {
849      listener.onDataChange(index);
850    })
851  }
852
853  notifyDataDelete(index: number): void {
854    this.listeners.forEach(listener => {
855      listener.onDataDelete(index);
856    })
857  }
858
859  notifyDataMove(from: number, to: number): void {
860    this.listeners.forEach(listener => {
861      listener.onDataMove(from, to);
862    })
863  }
864}
865
866class MyDataSource extends BasicDataSource {
867  private dataArray: string[] = [];
868
869  public totalCount(): number {
870    return this.dataArray.length;
871  }
872
873  public getData(index: number): string {
874    return this.dataArray[index];
875  }
876
877  public addData(index: number, data: string): void {
878    this.dataArray.splice(index, 0, data);
879    this.notifyDataAdd(index);
880  }
881
882  public pushData(data: string): void {
883    this.dataArray.push(data);
884  }
885  
886  public deleteData(index: number): void {
887    this.dataArray.splice(index, 1);
888    this.notifyDataDelete(index);
889  }
890  
891  public changeData(index: number): void {
892    this.notifyDataChange(index);
893  }
894    
895  public reloadData(): void {
896    this.notifyDataReload();
897  }
898    
899  public modifyAllData(): void {
900    this.dataArray = this.dataArray.map((item: string) => {
901        return item + '0';
902    })
903  }
904}
905
906@Entry
907@Component
908struct MyComponent {
909  private moved: number[] = [];
910  private data: MyDataSource = new MyDataSource();
911
912  aboutToAppear() {
913    for (let i = 0; i <= 20; i++) {
914      this.data.pushData(`Hello ${i}`)
915    }
916  }
917
918  build() {
919    List({ space: 3 }) {
920      LazyForEach(this.data, (item: string, index: number) => {
921        ListItem() {
922          Row() {
923            Text(item).fontSize(50)
924              .onAppear(() => {
925                console.info("appear:" + item)
926              })
927          }.margin({ left: 10, right: 10 })
928        }
929        .onClick(() => {
930          this.data.modifyAllData();
931          this.data.reloadData();
932        })
933      }, (item: string) => item)
934    }.cachedCount(5)
935  }
936}
937```
938
939当我们点击`LazyForEach`的子组件时,首先调用`data`的`modifyAllData`方法改变了数据源中的所有数据,然后调用数据源的`reloadData`方法,在该方法内会调用`notifyDataReload`方法。在`notifyDataReload`方法内会又调用`listener.onDataReloaded`方法,通知`LazyForEach`需要重建所有子节点。`LazyForEach`会将原所有数据项和新所有数据项一一做键值比对,若有相同键值则使用缓存,若键值不同则重新构建。
940
941运行效果如下图所示。
942
943**图7**  LazyForEach改变多个数据  
944![LazyForEach-Reload-Data](./figures/LazyForEach-Reload-Data.gif)
945
946#### 精准批量修改数据
947
948```ts
949class BasicDataSource implements IDataSource {
950  private listeners: DataChangeListener[] = [];
951  private originDataArray: string[] = [];
952
953  public totalCount(): number {
954    return 0;
955  }
956
957  public getData(index: number): string {
958    return this.originDataArray[index];
959  }
960
961  registerDataChangeListener(listener: DataChangeListener): void {
962    if (this.listeners.indexOf(listener) < 0) {
963      console.info('add listener');
964      this.listeners.push(listener);
965    }
966  }
967
968  unregisterDataChangeListener(listener: DataChangeListener): void {
969    const pos = this.listeners.indexOf(listener);
970    if (pos >= 0) {
971      console.info('remove listener');
972      this.listeners.splice(pos, 1);
973    }
974  }
975
976  notifyDatasetChange(operations: DataOperation[]): void {
977    this.listeners.forEach(listener => {
978      listener.onDatasetChange(operations);
979    })
980  }
981}
982
983class MyDataSource extends BasicDataSource {
984  private dataArray: string[] = [];
985
986  public totalCount(): number {
987    return this.dataArray.length;
988  }
989
990  public getData(index: number): string {
991    return this.dataArray[index];
992  }
993
994  public operateData(): void {
995    console.info(JSON.stringify(this.dataArray));
996    this.dataArray.splice(4, 0, this.dataArray[1]);
997    this.dataArray.splice(1, 1);
998    let temp = this.dataArray[4];
999    this.dataArray[4] = this.dataArray[6];
1000    this.dataArray[6] = temp
1001    this.dataArray.splice(8, 0, 'Hello 1', 'Hello 2');
1002    this.dataArray.splice(12, 2);
1003    console.info(JSON.stringify(this.dataArray));
1004    this.notifyDatasetChange([
1005      { type: DataOperationType.MOVE, index: { from: 1, to: 3 } },
1006      { type: DataOperationType.EXCHANGE, index: { start: 4, end: 6 } },
1007      { type: DataOperationType.ADD, index: 8, count: 2 },
1008      { type: DataOperationType.DELETE, index: 10, count: 2 }]);
1009  }
1010
1011  public init(): void {
1012    this.dataArray.splice(0, 0, 'Hello a', 'Hello b', 'Hello c', 'Hello d', 'Hello e', 'Hello f', 'Hello g', 'Hello h',
1013      'Hello i', 'Hello j', 'Hello k', 'Hello l', 'Hello m', 'Hello n', 'Hello o', 'Hello p', 'Hello q', 'Hello r');
1014  }
1015}
1016
1017@Entry
1018@Component
1019struct MyComponent {
1020  private data: MyDataSource = new MyDataSource();
1021
1022  aboutToAppear() {
1023    this.data.init()
1024  }
1025
1026  build() {
1027    Column() {
1028      Text('第二项数据移动到第四项处,第五项数据和第七项数据交换,第九项开始添加数据 "Hello 1" "Hello 2", 第十一项开始删除两个数据')
1029        .fontSize(10)
1030        .backgroundColor(Color.Blue)
1031        .fontColor(Color.White)
1032        .borderRadius(50)
1033        .padding(5)
1034        .onClick(() => {
1035          this.data.operateData();
1036        })
1037      List({ space: 3 }) {
1038        LazyForEach(this.data, (item: string, index: number) => {
1039          ListItem() {
1040            Row() {
1041              Text(item).fontSize(35)
1042                .onAppear(() => {
1043                  console.info("appear:" + item)
1044                })
1045            }.margin({ left: 10, right: 10 })
1046          }
1047
1048        }, (item: string) => item + new Date().getTime())
1049      }.cachedCount(5)
1050    }
1051  }
1052}
1053```
1054
1055onDatasetChange接口由开发者一次性通知LazyForEach应该做哪些操作。上述例子展示了LazyForEach同时进行数据添加、删除、移动、交换的操作。  
1056
1057**图8**  LazyForEach改变多个数据  
1058
1059![LazyForEach-Change-MultiData](./figures/LazyForEach-Change-MultiData.gif)  
1060
1061第二个例子,直接给数组赋值,不涉及 splice 操作。operations直接从比较原数组和新数组得到。
1062```ts
1063class BasicDataSource implements IDataSource {
1064  private listeners: DataChangeListener[] = [];
1065  private originDataArray: string[] = [];
1066
1067  public totalCount(): number {
1068    return 0;
1069  }
1070
1071  public getData(index: number): string {
1072    return this.originDataArray[index];
1073  }
1074
1075  registerDataChangeListener(listener: DataChangeListener): void {
1076    if (this.listeners.indexOf(listener) < 0) {
1077      console.info('add listener');
1078      this.listeners.push(listener);
1079    }
1080  }
1081
1082  unregisterDataChangeListener(listener: DataChangeListener): void {
1083    const pos = this.listeners.indexOf(listener);
1084    if (pos >= 0) {
1085      console.info('remove listener');
1086      this.listeners.splice(pos, 1);
1087    }
1088  }
1089
1090  notifyDatasetChange(operations: DataOperation[]): void {
1091    this.listeners.forEach(listener => {
1092      listener.onDatasetChange(operations);
1093    })
1094  }
1095}
1096
1097class MyDataSource extends BasicDataSource {
1098  private dataArray: string[] = [];
1099
1100  public totalCount(): number {
1101    return this.dataArray.length;
1102  }
1103
1104  public getData(index: number): string {
1105    return this.dataArray[index];
1106  }
1107
1108  public operateData(): void {
1109    this.dataArray =
1110      ['Hello x', 'Hello 1', 'Hello 2', 'Hello b', 'Hello c', 'Hello e', 'Hello d', 'Hello f', 'Hello g', 'Hello h']
1111    this.notifyDatasetChange([
1112      { type: DataOperationType.CHANGE, index: 0 },
1113      { type: DataOperationType.ADD, index: 1, count: 2 },
1114      { type: DataOperationType.EXCHANGE, index: { start: 3, end: 4 } },
1115    ]);
1116  }
1117
1118  public init(): void {
1119    this.dataArray = ['Hello a', 'Hello b', 'Hello c', 'Hello d', 'Hello e', 'Hello f', 'Hello g', 'Hello h'];
1120  }
1121}
1122
1123@Entry
1124@Component
1125struct MyComponent {
1126  private data: MyDataSource = new MyDataSource();
1127
1128  aboutToAppear() {
1129    this.data.init()
1130  }
1131
1132  build() {
1133    Column() {
1134      Text('Multi-Data Change')
1135        .fontSize(10)
1136        .backgroundColor(Color.Blue)
1137        .fontColor(Color.White)
1138        .borderRadius(50)
1139        .padding(5)
1140        .onClick(() => {
1141          this.data.operateData();
1142        })
1143      List({ space: 3 }) {
1144        LazyForEach(this.data, (item: string, index: number) => {
1145          ListItem() {
1146            Row() {
1147              Text(item).fontSize(35)
1148                .onAppear(() => {
1149                  console.info("appear:" + item)
1150                })
1151            }.margin({ left: 10, right: 10 })
1152          }
1153
1154        }, (item: string) => item + new Date().getTime())
1155      }.cachedCount(5)
1156    }
1157  }
1158}
1159```
1160**图9**  LazyForEach改变多个数据
1161
1162![LazyForEach-Change-MultiData2](./figures/LazyForEach-Change-MultiData2.gif)  
1163
1164使用该接口时有如下注意事项。
1165
11661. onDatasetChange与其它操作数据的接口不能混用。
11672. 传入onDatasetChange的operations,其中每一项operation的index均从修改前的原数组内寻找。因此,operations中的index跟操作Datasource中的index不总是一一对应的,而且不能是负数。  
1168第一个例子清楚地显示了这一点:
1169```ts
1170// 修改之前的数组
1171["Hello a","Hello b","Hello c","Hello d","Hello e","Hello f","Hello g","Hello h","Hello i","Hello j","Hello k","Hello l","Hello m","Hello n","Hello o","Hello p","Hello q","Hello r"]
1172// 修改之后的数组
1173["Hello a","Hello c","Hello d","Hello b","Hello g","Hello f","Hello e","Hello h","Hello 1","Hello 2","Hello i","Hello j","Hello m","Hello n","Hello o","Hello p","Hello q","Hello r"]
1174```
1175"Hello b" 从第2项变成第4项,因此第一个 operation 为 `{ type: DataOperationType.MOVE, index: { from: 1, to: 3 } }`  
1176"Hello e" 跟 "Hello g" 对调了,而 "Hello e" 在修改前的原数组中的 index=4,"Hello g" 在修改前的原数组中的 index=6, 因此第二个 operation 为 `{ type: DataOperationType.EXCHANGE, index: { start: 4, end: 6 } }`  
1177"Hello 1","Hello 2" 在 "Hello h" 之后插入,而 "Hello h" 在修改前的原数组中的 index=7,因此第三个 operation 为 `{ type: DataOperationType.ADD, index: 8, count: 2 }`  
1178"Hello k","Hello l" 被删除了,而 "Hello k" 在原数组中的 index=10,因此第四个 operation 为 `{ type: DataOperationType.DELETE, index: 10, count: 2 }`  
1179
11803. 调用一次onDatasetChange,一个index对应的数据只能被操作一次,若被操作多次,LazyForEach仅使第一个操作生效。
11814. 部分操作可以由开发者传入键值,LazyForEach不会再去重复调用keygenerator获取键值,需要开发者保证传入的键值的正确性。
11825. 若本次操作集合中有RELOAD操作,则其余操作全不生效。
1183
1184
1185
1186- ### 改变数据子属性
1187
1188若仅靠`LazyForEach`的刷新机制,当`item`变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件结构较为复杂的情况下,靠改变键值去刷新渲染性能较低。因此框架提供了`@Observed`与@`ObjectLink`机制进行深度观测,可以做到仅刷新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种刷新方式。
1189
1190```ts
1191class BasicDataSource implements IDataSource {
1192  private listeners: DataChangeListener[] = [];
1193  private originDataArray: StringData[] = [];
1194
1195  public totalCount(): number {
1196    return 0;
1197  }
1198
1199  public getData(index: number): StringData {
1200    return this.originDataArray[index];
1201  }
1202
1203  registerDataChangeListener(listener: DataChangeListener): void {
1204    if (this.listeners.indexOf(listener) < 0) {
1205      console.info('add listener');
1206      this.listeners.push(listener);
1207    }
1208  }
1209
1210  unregisterDataChangeListener(listener: DataChangeListener): void {
1211    const pos = this.listeners.indexOf(listener);
1212    if (pos >= 0) {
1213      console.info('remove listener');
1214      this.listeners.splice(pos, 1);
1215    }
1216  }
1217
1218  notifyDataReload(): void {
1219    this.listeners.forEach(listener => {
1220      listener.onDataReloaded();
1221    })
1222  }
1223
1224  notifyDataAdd(index: number): void {
1225    this.listeners.forEach(listener => {
1226      listener.onDataAdd(index);
1227    })
1228  }
1229
1230  notifyDataChange(index: number): void {
1231    this.listeners.forEach(listener => {
1232      listener.onDataChange(index);
1233    })
1234  }
1235
1236  notifyDataDelete(index: number): void {
1237    this.listeners.forEach(listener => {
1238      listener.onDataDelete(index);
1239    })
1240  }
1241
1242  notifyDataMove(from: number, to: number): void {
1243    this.listeners.forEach(listener => {
1244      listener.onDataMove(from, to);
1245    })
1246  }
1247}
1248
1249class MyDataSource extends BasicDataSource {
1250  private dataArray: StringData[] = [];
1251
1252  public totalCount(): number {
1253    return this.dataArray.length;
1254  }
1255
1256  public getData(index: number): StringData {
1257    return this.dataArray[index];
1258  }
1259
1260  public addData(index: number, data: StringData): void {
1261    this.dataArray.splice(index, 0, data);
1262    this.notifyDataAdd(index);
1263  }
1264
1265  public pushData(data: StringData): void {
1266    this.dataArray.push(data);
1267    this.notifyDataAdd(this.dataArray.length - 1);
1268  }
1269}
1270
1271@Observed
1272class StringData {
1273  message: string;
1274  constructor(message: string) {
1275    this.message = message;
1276  }  
1277}
1278
1279@Entry
1280@Component
1281struct MyComponent {
1282  private moved: number[] = [];
1283  @State data: MyDataSource = new MyDataSource();
1284
1285  aboutToAppear() {
1286    for (let i = 0; i <= 20; i++) {
1287      this.data.pushData(new StringData(`Hello ${i}`));
1288    }
1289  }
1290
1291  build() {
1292    List({ space: 3 }) {
1293      LazyForEach(this.data, (item: StringData, index: number) => {
1294        ListItem() {
1295          ChildComponent({data: item})
1296        }
1297        .onClick(() => {
1298          item.message += '0';
1299        })
1300      }, (item: StringData, index: number) => index.toString())
1301    }.cachedCount(5)
1302  }
1303}
1304
1305@Component
1306struct ChildComponent {
1307  @ObjectLink data: StringData
1308  build() {
1309    Row() {
1310      Text(this.data.message).fontSize(50)
1311        .onAppear(() => {
1312          console.info("appear:" + this.data.message)
1313        })
1314    }.margin({ left: 10, right: 10 })
1315  }
1316}
1317```
1318
1319此时点击`LazyForEach`子组件改变`item.message`时,重渲染依赖的是`ChildComponent`的`@ObjectLink`成员变量对其子属性的监听,此时框架只会刷新`Text(this.data.message)`,不会去重建整个`ListItem`子组件。
1320
1321**图10**  LazyForEach改变数据子属性  
1322![LazyForEach-Change-SubProperty](./figures/LazyForEach-Change-SubProperty.gif)
1323
1324## 拖拽排序
1325当LazyForEach在List组件下使用,并且设置了onMove事件,可以使能拖拽排序。拖拽排序离手后,如果数据位置发生变化,则会触发onMove事件,上报数据移动原始索引号和目标索引号。在onMove事件中,需要根据上报的起始索引号和目标索引号修改数据源。onMove中修改数据源不需要调用DataChangeListener中接口通知数据源变化。
1326
1327```ts
1328class BasicDataSource implements IDataSource {
1329  private listeners: DataChangeListener[] = [];
1330  private originDataArray: string[] = [];
1331
1332  public totalCount(): number {
1333    return 0;
1334  }
1335
1336  public getData(index: number): string {
1337    return this.originDataArray[index];
1338  }
1339
1340  registerDataChangeListener(listener: DataChangeListener): void {
1341    if (this.listeners.indexOf(listener) < 0) {
1342      console.info('add listener');
1343      this.listeners.push(listener);
1344    }
1345  }
1346
1347  unregisterDataChangeListener(listener: DataChangeListener): void {
1348    const pos = this.listeners.indexOf(listener);
1349    if (pos >= 0) {
1350      console.info('remove listener');
1351      this.listeners.splice(pos, 1);
1352    }
1353  }
1354
1355  notifyDataReload(): void {
1356    this.listeners.forEach(listener => {
1357      listener.onDataReloaded();
1358    })
1359  }
1360
1361  notifyDataAdd(index: number): void {
1362    this.listeners.forEach(listener => {
1363      listener.onDataAdd(index);
1364    })
1365  }
1366
1367  notifyDataChange(index: number): void {
1368    this.listeners.forEach(listener => {
1369      listener.onDataChange(index);
1370    })
1371  }
1372
1373  notifyDataDelete(index: number): void {
1374    this.listeners.forEach(listener => {
1375      listener.onDataDelete(index);
1376    })
1377  }
1378
1379  notifyDataMove(from: number, to: number): void {
1380    this.listeners.forEach(listener => {
1381      listener.onDataMove(from, to);
1382    })
1383  }
1384}
1385
1386class MyDataSource extends BasicDataSource {
1387  private dataArray: string[] = [];
1388
1389  public totalCount(): number {
1390    return this.dataArray.length;
1391  }
1392
1393  public getData(index: number): string {
1394    return this.dataArray[index];
1395  }
1396
1397  public addData(index: number, data: string): void {
1398    this.dataArray.splice(index, 0, data);
1399    this.notifyDataAdd(index);
1400  }
1401
1402  public moveDataWithoutNotify(from: number, to: number): void {
1403    let tmp = this.dataArray.splice(from, 1);
1404    this.dataArray.splice(to, 0, tmp[0])
1405  }
1406
1407  public pushData(data: string): void {
1408    this.dataArray.push(data);
1409    this.notifyDataAdd(this.dataArray.length - 1);
1410  }
1411
1412  public deleteData(index: number): void {
1413    this.dataArray.splice(index, 1);
1414    this.notifyDataDelete(index);
1415  }
1416}
1417
1418@Entry
1419@Component
1420struct Parent {
1421  private data: MyDataSource = new MyDataSource();
1422
1423  build() {
1424    Row() {
1425      List() {
1426        LazyForEach(this.data, (item: string) => {
1427            ListItem() {
1428              Text(item.toString())
1429                .fontSize(16)
1430                .textAlign(TextAlign.Center)
1431                .size({height: 100, width: "100%"})
1432            }.margin(10)
1433            .borderRadius(10)
1434            .backgroundColor("#FFFFFFFF")
1435          }, (item: string) => item)
1436          .onMove((from:number, to:number)=>{
1437            this.data.moveDataWithoutNotify(from, to)
1438          })
1439      }
1440      .width('100%')
1441      .height('100%')
1442      .backgroundColor("#FFDCDCDC")
1443    }
1444  }
1445  aboutToAppear(): void {
1446    for (let i = 0; i < 100; i++) {
1447      this.data.pushData(i.toString())
1448    }
1449  }
1450}
1451```
1452
1453**图11** LazyForEach拖拽排序效果图  
1454![LazyForEach-Drag-Sort](figures/ForEach-Drag-Sort.gif)
1455
1456## 常见使用问题
1457
1458### 渲染结果非预期
1459
1460  ```ts
1461  class BasicDataSource implements IDataSource {
1462    private listeners: DataChangeListener[] = [];
1463    private originDataArray: string[] = [];
1464  
1465    public totalCount(): number {
1466      return 0;
1467    }
1468  
1469    public getData(index: number): string {
1470      return this.originDataArray[index];
1471    }
1472  
1473    registerDataChangeListener(listener: DataChangeListener): void {
1474      if (this.listeners.indexOf(listener) < 0) {
1475        console.info('add listener');
1476        this.listeners.push(listener);
1477      }
1478    }
1479  
1480    unregisterDataChangeListener(listener: DataChangeListener): void {
1481      const pos = this.listeners.indexOf(listener);
1482      if (pos >= 0) {
1483        console.info('remove listener');
1484        this.listeners.splice(pos, 1);
1485      }
1486    }
1487  
1488    notifyDataReload(): void {
1489      this.listeners.forEach(listener => {
1490        listener.onDataReloaded();
1491      })
1492    }
1493  
1494    notifyDataAdd(index: number): void {
1495      this.listeners.forEach(listener => {
1496        listener.onDataAdd(index);
1497      })
1498    }
1499  
1500    notifyDataChange(index: number): void {
1501      this.listeners.forEach(listener => {
1502        listener.onDataChange(index);
1503      })
1504    }
1505  
1506    notifyDataDelete(index: number): void {
1507      this.listeners.forEach(listener => {
1508        listener.onDataDelete(index);
1509      })
1510    }
1511  
1512    notifyDataMove(from: number, to: number): void {
1513      this.listeners.forEach(listener => {
1514        listener.onDataMove(from, to);
1515      })
1516    }
1517  }
1518  
1519  class MyDataSource extends BasicDataSource {
1520    private dataArray: string[] = [];
1521  
1522    public totalCount(): number {
1523      return this.dataArray.length;
1524    }
1525  
1526    public getData(index: number): string {
1527      return this.dataArray[index];
1528    }
1529  
1530    public addData(index: number, data: string): void {
1531      this.dataArray.splice(index, 0, data);
1532      this.notifyDataAdd(index);
1533    }
1534  
1535    public pushData(data: string): void {
1536      this.dataArray.push(data);
1537      this.notifyDataAdd(this.dataArray.length - 1);
1538    }
1539    
1540    public deleteData(index: number): void {
1541      this.dataArray.splice(index, 1);
1542      this.notifyDataDelete(index);
1543    }
1544  }
1545  
1546  @Entry
1547  @Component
1548  struct MyComponent {
1549    private data: MyDataSource = new MyDataSource();
1550  
1551    aboutToAppear() {
1552      for (let i = 0; i <= 20; i++) {
1553        this.data.pushData(`Hello ${i}`)
1554      }
1555    }
1556  
1557    build() {
1558      List({ space: 3 }) {
1559        LazyForEach(this.data, (item: string, index: number) => {
1560          ListItem() {
1561            Row() {
1562              Text(item).fontSize(50)
1563                .onAppear(() => {
1564                  console.info("appear:" + item)
1565                })
1566            }.margin({ left: 10, right: 10 })
1567          }
1568          .onClick(() => {
1569            // 点击删除子组件
1570            this.data.deleteData(index);
1571          })
1572        }, (item: string) => item)
1573      }.cachedCount(5)
1574    }
1575  }
1576  ```
1577
1578  **图12**  LazyForEach删除数据非预期  
1579  ![LazyForEach-Render-Not-Expected](./figures/LazyForEach-Render-Not-Expected.gif)
1580
1581  当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其`index`均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的`index`,其`itemGenerator`中的`index`并没有发生变化,所以删除结果和预期不符。
1582
1583  修复代码如下所示。
1584
1585  ```ts
1586  class BasicDataSource implements IDataSource {
1587    private listeners: DataChangeListener[] = [];
1588    private originDataArray: string[] = [];
1589  
1590    public totalCount(): number {
1591      return 0;
1592    }
1593  
1594    public getData(index: number): string {
1595      return this.originDataArray[index];
1596    }
1597  
1598    registerDataChangeListener(listener: DataChangeListener): void {
1599      if (this.listeners.indexOf(listener) < 0) {
1600        console.info('add listener');
1601        this.listeners.push(listener);
1602      }
1603    }
1604  
1605    unregisterDataChangeListener(listener: DataChangeListener): void {
1606      const pos = this.listeners.indexOf(listener);
1607      if (pos >= 0) {
1608        console.info('remove listener');
1609        this.listeners.splice(pos, 1);
1610      }
1611    }
1612  
1613    notifyDataReload(): void {
1614      this.listeners.forEach(listener => {
1615        listener.onDataReloaded();
1616      })
1617    }
1618  
1619    notifyDataAdd(index: number): void {
1620      this.listeners.forEach(listener => {
1621        listener.onDataAdd(index);
1622      })
1623    }
1624  
1625    notifyDataChange(index: number): void {
1626      this.listeners.forEach(listener => {
1627        listener.onDataChange(index);
1628      })
1629    }
1630  
1631    notifyDataDelete(index: number): void {
1632      this.listeners.forEach(listener => {
1633        listener.onDataDelete(index);
1634      })
1635    }
1636  
1637    notifyDataMove(from: number, to: number): void {
1638      this.listeners.forEach(listener => {
1639        listener.onDataMove(from, to);
1640      })
1641    }
1642  }
1643  
1644  class MyDataSource extends BasicDataSource {
1645    private dataArray: string[] = [];
1646  
1647    public totalCount(): number {
1648      return this.dataArray.length;
1649    }
1650  
1651    public getData(index: number): string {
1652      return this.dataArray[index];
1653    }
1654  
1655    public addData(index: number, data: string): void {
1656      this.dataArray.splice(index, 0, data);
1657      this.notifyDataAdd(index);
1658    }
1659  
1660    public pushData(data: string): void {
1661      this.dataArray.push(data);
1662      this.notifyDataAdd(this.dataArray.length - 1);
1663    }
1664    
1665    public deleteData(index: number): void {
1666      this.dataArray.splice(index, 1);
1667      this.notifyDataDelete(index);
1668    }
1669      
1670    public reloadData(): void {
1671      this.notifyDataReload();
1672    }
1673  }
1674  
1675  @Entry
1676  @Component
1677  struct MyComponent {
1678    private data: MyDataSource = new MyDataSource();
1679  
1680    aboutToAppear() {
1681      for (let i = 0; i <= 20; i++) {
1682        this.data.pushData(`Hello ${i}`)
1683      }
1684    }
1685  
1686    build() {
1687      List({ space: 3 }) {
1688        LazyForEach(this.data, (item: string, index: number) => {
1689          ListItem() {
1690            Row() {
1691              Text(item).fontSize(50)
1692                .onAppear(() => {
1693                  console.info("appear:" + item)
1694                })
1695            }.margin({ left: 10, right: 10 })
1696          }
1697          .onClick(() => {
1698            // 点击删除子组件
1699            this.data.deleteData(index);
1700            // 重置所有子组件的index索引
1701            this.data.reloadData();
1702          })
1703        }, (item: string, index: number) => item + index.toString())
1704      }.cachedCount(5)
1705    }
1706  }
1707  ```
1708
1709  在删除一个数据项后调用`reloadData`方法,重建后面的数据项,以达到更新`index`索引的目的。要保证`reloadData`方法重建数据项,必须保证数据项能生成新的key。这里用了`item + index.toString()`保证被删除数据项后面的数据项都被重建。如果用`item + Data.now().toString()`替代,那么所有数据项都生成新的key,导致所有数据项都被重建。这种方法,效果是一样的,只是性能略差。
1710
1711  **图13**  修复LazyForEach删除数据非预期  
1712  ![LazyForEach-Render-Not-Expected-Repair](./figures/LazyForEach-Render-Not-Expected-Repair.gif)
1713
1714### 重渲染时图片闪烁
1715
1716  ```ts
1717  class BasicDataSource implements IDataSource {
1718    private listeners: DataChangeListener[] = [];
1719    private originDataArray: StringData[] = [];
1720  
1721    public totalCount(): number {
1722      return 0;
1723    }
1724  
1725    public getData(index: number): StringData {
1726      return this.originDataArray[index];
1727    }
1728  
1729    registerDataChangeListener(listener: DataChangeListener): void {
1730      if (this.listeners.indexOf(listener) < 0) {
1731        console.info('add listener');
1732        this.listeners.push(listener);
1733      }
1734    }
1735  
1736    unregisterDataChangeListener(listener: DataChangeListener): void {
1737      const pos = this.listeners.indexOf(listener);
1738      if (pos >= 0) {
1739        console.info('remove listener');
1740        this.listeners.splice(pos, 1);
1741      }
1742    }
1743  
1744    notifyDataReload(): void {
1745      this.listeners.forEach(listener => {
1746        listener.onDataReloaded();
1747      })
1748    }
1749  
1750    notifyDataAdd(index: number): void {
1751      this.listeners.forEach(listener => {
1752        listener.onDataAdd(index);
1753      })
1754    }
1755  
1756    notifyDataChange(index: number): void {
1757      this.listeners.forEach(listener => {
1758        listener.onDataChange(index);
1759      })
1760    }
1761  
1762    notifyDataDelete(index: number): void {
1763      this.listeners.forEach(listener => {
1764        listener.onDataDelete(index);
1765      })
1766    }
1767  
1768    notifyDataMove(from: number, to: number): void {
1769      this.listeners.forEach(listener => {
1770        listener.onDataMove(from, to);
1771      })
1772    }
1773  }
1774  
1775  class MyDataSource extends BasicDataSource {
1776    private dataArray: StringData[] = [];
1777  
1778    public totalCount(): number {
1779      return this.dataArray.length;
1780    }
1781  
1782    public getData(index: number): StringData {
1783      return this.dataArray[index];
1784    }
1785  
1786    public addData(index: number, data: StringData): void {
1787      this.dataArray.splice(index, 0, data);
1788      this.notifyDataAdd(index);
1789    }
1790  
1791    public pushData(data: StringData): void {
1792      this.dataArray.push(data);
1793      this.notifyDataAdd(this.dataArray.length - 1);
1794    }
1795      
1796    public reloadData(): void {
1797      this.notifyDataReload();
1798    }
1799  }
1800  
1801  class StringData {
1802    message: string;
1803    imgSrc: Resource;
1804    constructor(message: string, imgSrc: Resource) {
1805        this.message = message;
1806        this.imgSrc = imgSrc;
1807    }  
1808  }
1809  
1810  @Entry
1811  @Component
1812  struct MyComponent {
1813    private moved: number[] = [];
1814    private data: MyDataSource = new MyDataSource();
1815  
1816    aboutToAppear() {
1817      for (let i = 0; i <= 20; i++) {
1818        this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
1819      }
1820    }
1821  
1822    build() {
1823      List({ space: 3 }) {
1824        LazyForEach(this.data, (item: StringData, index: number) => {
1825          ListItem() {
1826            Column() {
1827              Text(item.message).fontSize(50)
1828                .onAppear(() => {
1829                  console.info("appear:" + item.message)
1830                })
1831              Image(item.imgSrc)
1832                .width(500)
1833                .height(200)
1834            }.margin({ left: 10, right: 10 })
1835          }
1836          .onClick(() => {
1837            item.message += '00';
1838            this.data.reloadData();
1839          })
1840        }, (item: StringData, index: number) => JSON.stringify(item))
1841      }.cachedCount(5)
1842    }
1843  }
1844  ```
1845
1846  **图14**  LazyForEach仅改变文字但是图片闪烁问题  
1847  ![LazyForEach-Image-Flush](./figures/LazyForEach-Image-Flush.gif)
1848
1849  在我们点击`ListItem`子组件时,我们只改变了数据项的`message`属性,但是`LazyForEach`的刷新机制会导致整个`ListItem`被重建。由于`Image`组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用`@ObjectLink`和`@Observed`去单独刷新使用了`item.message`的`Text`组件。
1850
1851  修复代码如下所示。
1852
1853  ```ts
1854  class BasicDataSource implements IDataSource {
1855    private listeners: DataChangeListener[] = [];
1856    private originDataArray: StringData[] = [];
1857  
1858    public totalCount(): number {
1859      return 0;
1860    }
1861  
1862    public getData(index: number): StringData {
1863      return this.originDataArray[index];
1864    }
1865  
1866    registerDataChangeListener(listener: DataChangeListener): void {
1867      if (this.listeners.indexOf(listener) < 0) {
1868        console.info('add listener');
1869        this.listeners.push(listener);
1870      }
1871    }
1872  
1873    unregisterDataChangeListener(listener: DataChangeListener): void {
1874      const pos = this.listeners.indexOf(listener);
1875      if (pos >= 0) {
1876        console.info('remove listener');
1877        this.listeners.splice(pos, 1);
1878      }
1879    }
1880  
1881    notifyDataReload(): void {
1882      this.listeners.forEach(listener => {
1883        listener.onDataReloaded();
1884      })
1885    }
1886  
1887    notifyDataAdd(index: number): void {
1888      this.listeners.forEach(listener => {
1889        listener.onDataAdd(index);
1890      })
1891    }
1892  
1893    notifyDataChange(index: number): void {
1894      this.listeners.forEach(listener => {
1895        listener.onDataChange(index);
1896      })
1897    }
1898  
1899    notifyDataDelete(index: number): void {
1900      this.listeners.forEach(listener => {
1901        listener.onDataDelete(index);
1902      })
1903    }
1904  
1905    notifyDataMove(from: number, to: number): void {
1906      this.listeners.forEach(listener => {
1907        listener.onDataMove(from, to);
1908      })
1909    }
1910  }
1911  
1912  class MyDataSource extends BasicDataSource {
1913    private dataArray: StringData[] = [];
1914  
1915    public totalCount(): number {
1916      return this.dataArray.length;
1917    }
1918  
1919    public getData(index: number): StringData {
1920      return this.dataArray[index];
1921    }
1922  
1923    public addData(index: number, data: StringData): void {
1924      this.dataArray.splice(index, 0, data);
1925      this.notifyDataAdd(index);
1926    }
1927  
1928    public pushData(data: StringData): void {
1929      this.dataArray.push(data);
1930      this.notifyDataAdd(this.dataArray.length - 1);
1931    }
1932  }
1933  
1934  // @Observed类装饰器 和 @ObjectLink 用于在涉及嵌套对象或数组的场景中进行双向数据同步
1935  @Observed
1936  class StringData {
1937    message: string;
1938    imgSrc: Resource;
1939    constructor(message: string, imgSrc: Resource) {
1940        this.message = message;
1941        this.imgSrc = imgSrc;
1942    }  
1943  }
1944  
1945  @Entry
1946  @Component
1947  struct MyComponent {
1948    // 用状态变量来驱动UI刷新,而不是通过Lazyforeach的api来驱动UI刷新
1949    @State data: MyDataSource = new MyDataSource();
1950  
1951    aboutToAppear() {
1952      for (let i = 0; i <= 20; i++) {
1953        this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
1954      }
1955    }
1956  
1957    build() {
1958      List({ space: 3 }) {
1959        LazyForEach(this.data, (item: StringData, index: number) => {
1960          ListItem() {
1961            ChildComponent({data: item})
1962          }
1963          .onClick(() => {
1964            item.message += '0';
1965          })
1966        }, (item: StringData, index: number) => index.toString())
1967      }.cachedCount(5)
1968    }
1969  }
1970  
1971  @Component
1972  struct ChildComponent {
1973    @ObjectLink data: StringData
1974    build() {
1975      Column() {
1976        Text(this.data.message).fontSize(50)
1977          .onAppear(() => {
1978            console.info("appear:" + this.data.message)
1979          })
1980        Image(this.data.imgSrc)
1981          .width(500)
1982          .height(200)
1983      }.margin({ left: 10, right: 10 })
1984    }
1985  }
1986  ```
1987
1988  **图15**  修复LazyForEach仅改变文字但是图片闪烁问题  
1989  ![LazyForEach-Image-Flush-Repair](./figures/LazyForEach-Image-Flush-Repair.gif)
1990
1991### @ObjectLink属性变化UI未更新
1992
1993  ```ts
1994  class BasicDataSource implements IDataSource {
1995    private listeners: DataChangeListener[] = [];
1996    private originDataArray: StringData[] = [];
1997  
1998    public totalCount(): number {
1999      return 0;
2000    }
2001  
2002    public getData(index: number): StringData {
2003      return this.originDataArray[index];
2004    }
2005  
2006    registerDataChangeListener(listener: DataChangeListener): void {
2007      if (this.listeners.indexOf(listener) < 0) {
2008        console.info('add listener');
2009        this.listeners.push(listener);
2010      }
2011    }
2012  
2013    unregisterDataChangeListener(listener: DataChangeListener): void {
2014      const pos = this.listeners.indexOf(listener);
2015      if (pos >= 0) {
2016        console.info('remove listener');
2017        this.listeners.splice(pos, 1);
2018      }
2019    }
2020  
2021    notifyDataReload(): void {
2022      this.listeners.forEach(listener => {
2023        listener.onDataReloaded();
2024      })
2025    }
2026  
2027    notifyDataAdd(index: number): void {
2028      this.listeners.forEach(listener => {
2029        listener.onDataAdd(index);
2030      })
2031    }
2032  
2033    notifyDataChange(index: number): void {
2034      this.listeners.forEach(listener => {
2035        listener.onDataChange(index);
2036      })
2037    }
2038  
2039    notifyDataDelete(index: number): void {
2040      this.listeners.forEach(listener => {
2041        listener.onDataDelete(index);
2042      })
2043    }
2044  
2045    notifyDataMove(from: number, to: number): void {
2046      this.listeners.forEach(listener => {
2047        listener.onDataMove(from, to);
2048      })
2049    }
2050  }
2051  
2052  class MyDataSource extends BasicDataSource {
2053    private dataArray: StringData[] = [];
2054  
2055    public totalCount(): number {
2056      return this.dataArray.length;
2057    }
2058  
2059    public getData(index: number): StringData {
2060      return this.dataArray[index];
2061    }
2062  
2063    public addData(index: number, data: StringData): void {
2064      this.dataArray.splice(index, 0, data);
2065      this.notifyDataAdd(index);
2066    }
2067  
2068    public pushData(data: StringData): void {
2069      this.dataArray.push(data);
2070      this.notifyDataAdd(this.dataArray.length - 1);
2071    }
2072  }
2073  
2074  @Observed
2075  class StringData {
2076    message: NestedString;
2077    constructor(message: NestedString) {
2078      this.message = message;
2079    }  
2080  }
2081  
2082  @Observed
2083  class NestedString {
2084    message: string;
2085    constructor(message: string) {
2086      this.message = message;
2087    }  
2088  }
2089  
2090  @Entry
2091  @Component
2092  struct MyComponent {
2093    private moved: number[] = [];
2094    @State data: MyDataSource = new MyDataSource();
2095  
2096    aboutToAppear() {
2097      for (let i = 0; i <= 20; i++) {
2098        this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
2099      }
2100    }
2101  
2102    build() {
2103      List({ space: 3 }) {
2104        LazyForEach(this.data, (item: StringData, index: number) => {
2105          ListItem() {
2106            ChildComponent({data: item})
2107          }
2108          .onClick(() => {
2109            item.message.message += '0';
2110          })
2111        }, (item: StringData, index: number) => JSON.stringify(item) + index.toString())
2112      }.cachedCount(5)
2113    }
2114  }
2115  
2116  @Component
2117  struct ChildComponent {
2118    @ObjectLink data: StringData
2119    build() {
2120      Row() {
2121        Text(this.data.message.message).fontSize(50)
2122          .onAppear(() => {
2123            console.info("appear:" + this.data.message.message)
2124          })
2125      }.margin({ left: 10, right: 10 })
2126    }
2127  }
2128  ```
2129
2130  **图16**  ObjectLink属性变化后UI未更新  
2131  ![LazyForEach-ObjectLink-NotRenderUI](./figures/LazyForEach-ObjectLink-NotRenderUI.gif)
2132  
2133  @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体[请查看@ObjectLink与@Observed的详细使用方法和限制条件](./arkts-observed-and-objectlink.md)。
2134  
2135  修复代码如下所示。
2136  
2137  ```ts
2138  class BasicDataSource implements IDataSource {
2139    private listeners: DataChangeListener[] = [];
2140    private originDataArray: StringData[] = [];
2141  
2142    public totalCount(): number {
2143      return 0;
2144    }
2145  
2146    public getData(index: number): StringData {
2147      return this.originDataArray[index];
2148    }
2149  
2150    registerDataChangeListener(listener: DataChangeListener): void {
2151      if (this.listeners.indexOf(listener) < 0) {
2152        console.info('add listener');
2153        this.listeners.push(listener);
2154      }
2155    }
2156  
2157    unregisterDataChangeListener(listener: DataChangeListener): void {
2158      const pos = this.listeners.indexOf(listener);
2159      if (pos >= 0) {
2160        console.info('remove listener');
2161        this.listeners.splice(pos, 1);
2162      }
2163    }
2164  
2165    notifyDataReload(): void {
2166      this.listeners.forEach(listener => {
2167        listener.onDataReloaded();
2168      })
2169    }
2170  
2171    notifyDataAdd(index: number): void {
2172      this.listeners.forEach(listener => {
2173        listener.onDataAdd(index);
2174      })
2175    }
2176  
2177    notifyDataChange(index: number): void {
2178      this.listeners.forEach(listener => {
2179        listener.onDataChange(index);
2180      })
2181    }
2182  
2183    notifyDataDelete(index: number): void {
2184      this.listeners.forEach(listener => {
2185        listener.onDataDelete(index);
2186      })
2187    }
2188  
2189    notifyDataMove(from: number, to: number): void {
2190      this.listeners.forEach(listener => {
2191        listener.onDataMove(from, to);
2192      })
2193    }
2194  }
2195  
2196  class MyDataSource extends BasicDataSource {
2197    private dataArray: StringData[] = [];
2198  
2199    public totalCount(): number {
2200      return this.dataArray.length;
2201    }
2202  
2203    public getData(index: number): StringData {
2204      return this.dataArray[index];
2205    }
2206  
2207    public addData(index: number, data: StringData): void {
2208      this.dataArray.splice(index, 0, data);
2209      this.notifyDataAdd(index);
2210    }
2211  
2212    public pushData(data: StringData): void {
2213      this.dataArray.push(data);
2214      this.notifyDataAdd(this.dataArray.length - 1);
2215    }
2216  }
2217  
2218  @Observed
2219  class StringData {
2220    message: NestedString;
2221    constructor(message: NestedString) {
2222      this.message = message;
2223    }  
2224  }
2225  
2226  @Observed
2227  class NestedString {
2228    message: string;
2229    constructor(message: string) {
2230      this.message = message;
2231    }  
2232  }
2233  
2234  @Entry
2235  @Component
2236  struct MyComponent {
2237    private moved: number[] = [];
2238    @State data: MyDataSource = new MyDataSource();
2239  
2240    aboutToAppear() {
2241      for (let i = 0; i <= 20; i++) {
2242        this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
2243      }
2244    }
2245  
2246    build() {
2247      List({ space: 3 }) {
2248        LazyForEach(this.data, (item: StringData, index: number) => {
2249          ListItem() {
2250            ChildComponent({data: item})
2251          }
2252          .onClick(() => {
2253            // @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到
2254            item.message = new NestedString(item.message.message + '0');
2255          })
2256        }, (item: StringData, index: number) => JSON.stringify(item) + index.toString())
2257      }.cachedCount(5)
2258    }
2259  }
2260  
2261  @Component
2262  struct ChildComponent {
2263    @ObjectLink data: StringData
2264    build() {
2265      Row() {
2266        Text(this.data.message.message).fontSize(50)
2267          .onAppear(() => {
2268            console.info("appear:" + this.data.message.message)
2269          })
2270      }.margin({ left: 10, right: 10 })
2271    }
2272  }
2273  ```
2274  
2275  **图17**  修复ObjectLink属性变化后UI更新  
2276  ![LazyForEach-ObjectLink-NotRenderUI-Repair](./figures/LazyForEach-ObjectLink-NotRenderUI-Repair.gif)
2277
2278### 在List内使用屏幕闪烁
2279在List的onScrollIndex方法中调用onDataReloaded有产生屏幕闪烁的风险。
2280
2281```ts
2282class BasicDataSource implements IDataSource {
2283  private listeners: DataChangeListener[] = [];
2284  private originDataArray: string[] = [];
2285
2286  public totalCount(): number {
2287    return 0;
2288  }
2289
2290  public getData(index: number): string {
2291    return this.originDataArray[index];
2292  }
2293
2294  registerDataChangeListener(listener: DataChangeListener): void {
2295    if (this.listeners.indexOf(listener) < 0) {
2296      console.info('add listener');
2297      this.listeners.push(listener);
2298    }
2299  }
2300
2301  unregisterDataChangeListener(listener: DataChangeListener): void {
2302    const pos = this.listeners.indexOf(listener);
2303    if (pos >= 0) {
2304      console.info('remove listener');
2305      this.listeners.splice(pos, 1);
2306    }
2307  }
2308
2309  notifyDataReload(): void {
2310    this.listeners.forEach(listener => {
2311      listener.onDataReloaded();
2312    })
2313  }
2314
2315  notifyDataAdd(index: number): void {
2316    this.listeners.forEach(listener => {
2317      listener.onDataAdd(index);
2318    })
2319  }
2320
2321  notifyDataChange(index: number): void {
2322    this.listeners.forEach(listener => {
2323      listener.onDataChange(index);
2324    })
2325  }
2326
2327  notifyDataDelete(index: number): void {
2328    this.listeners.forEach(listener => {
2329      listener.onDataDelete(index);
2330    })
2331  }
2332
2333  notifyDataMove(from: number, to: number): void {
2334    this.listeners.forEach(listener => {
2335      listener.onDataMove(from, to);
2336    })
2337  }
2338
2339  notifyDatasetChange(operations: DataOperation[]):void{
2340    this.listeners.forEach(listener => {
2341      listener.onDatasetChange(operations);
2342    })
2343  }
2344}
2345
2346class MyDataSource extends BasicDataSource {
2347  private dataArray: string[] = [];
2348
2349  public totalCount(): number {
2350    return this.dataArray.length;
2351  }
2352
2353  public getData(index: number): string {
2354    return this.dataArray[index];
2355  }
2356
2357  public addData(index: number, data: string): void {
2358    this.dataArray.splice(index, 0, data);
2359    this.notifyDataAdd(index);
2360  }
2361
2362  public pushData(data: string): void {
2363    this.dataArray.push(data);
2364    this.notifyDataAdd(this.dataArray.length - 1);
2365  }
2366
2367  public deleteData(index: number): void {
2368    this.dataArray.splice(index, 1);
2369    this.notifyDataDelete(index);
2370  }
2371
2372  public changeData(index: number): void {
2373    this.notifyDataChange(index);
2374  }
2375
2376  operateData():void {
2377    const totalCount = this.dataArray.length;
2378    const batch=5;
2379    for (let i = totalCount; i < totalCount + batch; i++) {
2380      this.dataArray.push(`Hello ${i}`)
2381    }
2382    this.notifyDataReload();
2383  }
2384}
2385
2386@Entry
2387@Component
2388struct MyComponent {
2389  private moved: number[] = [];
2390  private data: MyDataSource = new MyDataSource();
2391
2392  aboutToAppear() {
2393    for (let i = 0; i <= 10; i++) {
2394      this.data.pushData(`Hello ${i}`)
2395    }
2396  }
2397
2398  build() {
2399    List({ space: 3 }) {
2400      LazyForEach(this.data, (item: string, index: number) => {
2401        ListItem() {
2402          Row() {
2403            Text(item)
2404              .width('100%')
2405              .height(80)
2406              .backgroundColor(Color.Gray)
2407              .onAppear(() => {
2408                console.info("appear:" + item)
2409              })
2410          }.margin({ left: 10, right: 10 })
2411        }
2412      }, (item: string) => item)
2413    }.cachedCount(10)
2414    .onScrollIndex((start, end, center) => {
2415      if (end === this.data.totalCount() - 1) {
2416        console.log('scroll to end')
2417        this.data.operateData();
2418      }
2419    })
2420  }
2421}
2422```
2423
2424当List下拉到底的时候,屏闪效果如下图  
2425![LazyForEach-Screen-Flicker](figures/LazyForEach-Screen-Flicker.gif)
2426
2427用onDatasetChange代替onDataReloaded,不仅可以修复闪屏的问题,还能提升加载性能。
2428
2429```ts
2430class BasicDataSource implements IDataSource {
2431  private listeners: DataChangeListener[] = [];
2432  private originDataArray: string[] = [];
2433
2434  public totalCount(): number {
2435    return 0;
2436  }
2437
2438  public getData(index: number): string {
2439    return this.originDataArray[index];
2440  }
2441
2442  registerDataChangeListener(listener: DataChangeListener): void {
2443    if (this.listeners.indexOf(listener) < 0) {
2444      console.info('add listener');
2445      this.listeners.push(listener);
2446    }
2447  }
2448
2449  unregisterDataChangeListener(listener: DataChangeListener): void {
2450    const pos = this.listeners.indexOf(listener);
2451    if (pos >= 0) {
2452      console.info('remove listener');
2453      this.listeners.splice(pos, 1);
2454    }
2455  }
2456
2457  notifyDataReload(): void {
2458    this.listeners.forEach(listener => {
2459      listener.onDataReloaded();
2460    })
2461  }
2462
2463  notifyDataAdd(index: number): void {
2464    this.listeners.forEach(listener => {
2465      listener.onDataAdd(index);
2466    })
2467  }
2468
2469  notifyDataChange(index: number): void {
2470    this.listeners.forEach(listener => {
2471      listener.onDataChange(index);
2472    })
2473  }
2474
2475  notifyDataDelete(index: number): void {
2476    this.listeners.forEach(listener => {
2477      listener.onDataDelete(index);
2478    })
2479  }
2480
2481  notifyDataMove(from: number, to: number): void {
2482    this.listeners.forEach(listener => {
2483      listener.onDataMove(from, to);
2484    })
2485  }
2486
2487  notifyDatasetChange(operations: DataOperation[]):void{
2488    this.listeners.forEach(listener => {
2489      listener.onDatasetChange(operations);
2490    })
2491  }
2492}
2493
2494class MyDataSource extends BasicDataSource {
2495  private dataArray: string[] = [];
2496
2497  public totalCount(): number {
2498    return this.dataArray.length;
2499  }
2500
2501  public getData(index: number): string {
2502    return this.dataArray[index];
2503  }
2504
2505  public addData(index: number, data: string): void {
2506    this.dataArray.splice(index, 0, data);
2507    this.notifyDataAdd(index);
2508  }
2509
2510  public pushData(data: string): void {
2511    this.dataArray.push(data);
2512    this.notifyDataAdd(this.dataArray.length - 1);
2513  }
2514
2515  public deleteData(index: number): void {
2516    this.dataArray.splice(index, 1);
2517    this.notifyDataDelete(index);
2518  }
2519
2520  public changeData(index: number): void {
2521    this.notifyDataChange(index);
2522  }
2523
2524  operateData():void {
2525    const totalCount = this.dataArray.length;
2526    const batch=5;
2527    for (let i = totalCount; i < totalCount + batch; i++) {
2528      this.dataArray.push(`Hello ${i}`)
2529    }
2530    // 替换 notifyDataReload
2531    this.notifyDatasetChange([{type:DataOperationType.ADD, index: totalCount-1, count:batch}])
2532  }
2533}
2534
2535@Entry
2536@Component
2537struct MyComponent {
2538  private moved: number[] = [];
2539  private data: MyDataSource = new MyDataSource();
2540
2541  aboutToAppear() {
2542    for (let i = 0; i <= 10; i++) {
2543      this.data.pushData(`Hello ${i}`)
2544    }
2545  }
2546
2547  build() {
2548    List({ space: 3 }) {
2549      LazyForEach(this.data, (item: string, index: number) => {
2550        ListItem() {
2551          Row() {
2552            Text(item)
2553              .width('100%')
2554              .height(80)
2555              .backgroundColor(Color.Gray)
2556              .onAppear(() => {
2557                console.info("appear:" + item)
2558              })
2559          }.margin({ left: 10, right: 10 })
2560        }
2561      }, (item: string) => item)
2562    }.cachedCount(10)
2563    .onScrollIndex((start, end, center) => {
2564      if (end === this.data.totalCount() - 1) {
2565        console.log('scroll to end')
2566        this.data.operateData();
2567      }
2568    })
2569  }
2570}
2571```
2572
2573修复后的效果如下图  
2574![LazyForEach-Screen-Flicker-Repair](figures/LazyForEach-Screen-Flicker-Repair.gif)