• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# LazyForEach:数据懒加载
2
3
4LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
5
6
7## 接口描述
8
9
10```ts
11LazyForEach(
12    dataSource: IDataSource,             // 需要进行数据迭代的数据源
13    itemGenerator: (item: any, index: number) => void,  // 子组件生成函数
14    keyGenerator?: (item: any, index: number) => string // 键值生成函数
15): void
16```
17
18**参数:**
19
20
21| 参数名        | 参数类型                                               | 必填 | 参数描述                                                     |
22| ------------- | ------------------------------------------------------ | ---- | ------------------------------------------------------------ |
23| dataSource    | [IDataSource](#idatasource类型说明)                    | 是   | LazyForEach数据源,需要开发者实现相关接口。                  |
24| itemGenerator | (item:&nbsp;any, index:number)&nbsp;=&gt;&nbsp;void  | 是   | 子组件生成函数,为数组中的每一个数据项创建一个子组件。<br/>**说明:**<br/>item是当前数据项,index是数据项索引值。<br/>itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须生成一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不允许使用ForEach和LazyForEach语句。 |
25| keyGenerator  | (item:&nbsp;any, index:number)&nbsp;=&gt;&nbsp;string | 否   | 键值生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。<br/>**说明:**<br/>item是当前数据项,index是数据项索引值。<br/>数据源中的每一个数据项生成的键值不能重复。 |
26
27## IDataSource类型说明
28
29```ts
30interface IDataSource {
31    totalCount(): number; // 获得数据总数
32    getData(index: number): Object; // 获取索引值对应的数据
33    registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
34    unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
35}
36```
37
38| 接口声明                                                     | 参数类型                                          | 说明                                                        |
39| ------------------------------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------- |
40| totalCount():&nbsp;number                                    | -                                                 | 获得数据总数。                                              |
41| getData(index:&nbsp;number):&nbsp;any                        | number                                            | 获取索引值index对应的数据。<br/>index:获取数据对应的索引值。 |
42| registerDataChangeListener(listener:[DataChangeListener](#datachangelistener类型说明)):&nbsp;void | [DataChangeListener](#datachangelistener类型说明) | 注册数据改变的监听器。<br/>listener:数据变化监听器         |
43| unregisterDataChangeListener(listener:[DataChangeListener](#datachangelistener类型说明)):&nbsp;void | [DataChangeListener](#datachangelistener类型说明) | 注销数据改变的监听器。<br/>listener:数据变化监听器         |
44
45## DataChangeListener类型说明
46
47```ts
48interface DataChangeListener {
49    onDataReloaded(): void; // 重新加载数据完成后调用
50    onDataAdded(index: number): void; // 添加数据完成后调用
51    onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
52    onDataDeleted(index: number): void; // 删除数据完成后调用
53    onDataChanged(index: number): void; // 改变数据完成后调用
54    onDataAdd(index: number): void; // 添加数据完成后调用
55    onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
56    onDataDelete(index: number): void; // 删除数据完成后调用
57    onDataChange(index: number): void; // 改变数据完成后调用
58}
59```
60
61| 接口声明                                                     | 参数类型                               | 说明                                                         |
62| ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------ |
63| onDataReloaded():&nbsp;void                                  | -                                      | 通知组件重新加载所有数据。<br/>键值没有变化的数据项会使用原先的子组件,键值发生变化的会重建子组件。 |
64| onDataAdd(index:&nbsp;number):&nbsp;void<sup>8+</sup>        | number                                 | 通知组件index的位置有数据添加。<br/>index:数据添加位置的索引值。 |
65| onDataMove(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void<sup>8+</sup> | from:&nbsp;number,<br/>to:&nbsp;number | 通知组件数据有移动。<br/>from:&nbsp;数据移动起始位置,to:&nbsp;数据移动目标位置。<br/>将from和to位置的数据进行交换。<br/>**说明:**<br/>数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。 |
66| onDataDelete(index: number):void<sup>8+</sup>                | number                                 | 通知组件删除index位置的数据并刷新LazyForEach的展示内容。<br/>index:数据删除位置的索引值。<br/>**说明:** <br/>需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。 |
67| onDataChange(index:&nbsp;number):&nbsp;void<sup>8+</sup>     | number                                 | 通知组件index的位置有数据有变化。<br/>index:数据变化位置的索引值。 |
68| onDataAdded(index:&nbsp;number):void<sup>(deprecated)</sup>  | number                                 | 通知组件index的位置有数据添加。<br/>从API 8开始,建议使用onDataAdd。<br/>index:数据添加位置的索引值。 |
69| onDataMoved(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void<sup>(deprecated)</sup> | from:&nbsp;number,<br/>to:&nbsp;number | 通知组件数据有移动。<br/>从API 8开始,建议使用onDataMove。<br/>from:&nbsp;数据移动起始位置,to:&nbsp;数据移动目标位置。<br/>将from和to位置的数据进行交换。<br/>**说明:**<br/>数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。 |
70| onDataDeleted(index: number):void<sup>(deprecated)</sup>     | number                                 | 通知组件删除index位置的数据并刷新LazyForEach的展示内容。<br/>从API 8开始,建议使用onDataDelete。<br/>index:数据删除位置的索引值。 |
71| onDataChanged(index:&nbsp;number):&nbsp;void<sup>(deprecated)</sup> | number                                 | 通知组件index的位置有数据有变化。<br/>从API 8开始,建议使用onDataChange。<br/>index:数据变化监听器。 |
72
73
74## 使用限制
75
76- 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属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
77- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
78- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
79- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
80- 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
81- LazyForEach必须使用DataChangeListener对象来进行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。
82- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。
83
84## 键值生成规则
85
86在`LazyForEach`循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。
87
88`LazyForEach`提供了一个名为`keyGenerator`的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义`keyGenerator`函数,则ArkUI框架会使用默认的键值生成函数,即`(item: any, index: number) => { return viewId + '-' + index.toString(); }`, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。
89
90## 组件创建规则
91
92在确定键值生成规则后,LazyForEach的第二个参数`itemGenerator`函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:[LazyForEach首次渲染](#首次渲染)和[LazyForEach非首次渲染](#非首次渲染)。
93
94### 首次渲染
95
96- ### 生成不同键值
97
98在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。
99
100```ts
101// Basic implementation of IDataSource to handle data listener
102class BasicDataSource implements IDataSource {
103  private listeners: DataChangeListener[] = [];
104  private originDataArray: string[] = [];
105
106  public totalCount(): number {
107    return 0;
108  }
109
110  public getData(index: number): string {
111    return this.originDataArray[index];
112  }
113
114  // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
115  registerDataChangeListener(listener: DataChangeListener): void {
116    if (this.listeners.indexOf(listener) < 0) {
117      console.info('add listener');
118      this.listeners.push(listener);
119    }
120  }
121
122  // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
123  unregisterDataChangeListener(listener: DataChangeListener): void {
124    const pos = this.listeners.indexOf(listener);
125    if (pos >= 0) {
126      console.info('remove listener');
127      this.listeners.splice(pos, 1);
128    }
129  }
130
131  // 通知LazyForEach组件需要重载所有子组件
132  notifyDataReload(): void {
133    this.listeners.forEach(listener => {
134      listener.onDataReloaded();
135    })
136  }
137
138  // 通知LazyForEach组件需要在index对应索引处添加子组件
139  notifyDataAdd(index: number): void {
140    this.listeners.forEach(listener => {
141      listener.onDataAdd(index);
142    })
143  }
144
145  // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
146  notifyDataChange(index: number): void {
147    this.listeners.forEach(listener => {
148      listener.onDataChange(index);
149    })
150  }
151
152  // 通知LazyForEach组件需要在index对应索引处删除该子组件
153  notifyDataDelete(index: number): void {
154    this.listeners.forEach(listener => {
155      listener.onDataDelete(index);
156    })
157  }
158
159  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
160  notifyDataMove(from: number, to: number): void {
161    this.listeners.forEach(listener => {
162      listener.onDataMove(from, to);
163    })
164  }
165}
166
167class MyDataSource extends BasicDataSource {
168  private dataArray: string[] = [];
169
170  public totalCount(): number {
171    return this.dataArray.length;
172  }
173
174  public getData(index: number): string {
175    return this.dataArray[index];
176  }
177
178  public addData(index: number, data: string): void {
179    this.dataArray.splice(index, 0, data);
180    this.notifyDataAdd(index);
181  }
182
183  public pushData(data: string): void {
184    this.dataArray.push(data);
185    this.notifyDataAdd(this.dataArray.length - 1);
186  }
187}
188
189@Entry
190@Component
191struct MyComponent {
192  private data: MyDataSource = new MyDataSource();
193
194  aboutToAppear() {
195    for (let i = 0; i <= 20; i++) {
196      this.data.pushData(`Hello ${i}`)
197    }
198  }
199
200  build() {
201    List({ space: 3 }) {
202      LazyForEach(this.data, (item: string) => {
203        ListItem() {
204          Row() {
205            Text(item).fontSize(50)
206              .onAppear(() => {
207                console.info("appear:" + item)
208              })
209          }.margin({ left: 10, right: 10 })
210        }
211      }, (item: string) => item)
212    }.cachedCount(5)
213  }
214}
215```
216
217在上述代码中,键值生成规则是`keyGenerator`函数的返回值`item`。在`LazyForEach`循环渲染时,其为数据源数组项依次生成键值`Hello 0`、`Hello 1` ... `Hello 20`,并创建对应的`ListItem`子组件渲染到界面上。
218
219运行效果如下图所示。
220
221**图1**  LazyForEach正常首次渲染
222![LazyForEach-Render-DifferentKey](./figures/LazyForEach-Render-DifferentKey.gif)
223
224- ### 键值相同时错误渲染
225
226当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,`LazyForEach`渲染的数据项键值均相同,在滑动过程中,`LazyForEach`会对划入划出当前页面的子组件进行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架可能存在取用缓存错误的情况,导致子组件渲染有问题。
227
228 ```ts
229class BasicDataSource implements IDataSource {
230  private listeners: DataChangeListener[] = [];
231  private originDataArray: string[] = [];
232
233  public totalCount(): number {
234    return 0;
235  }
236
237  public getData(index: number): string {
238    return this.originDataArray[index];
239  }
240
241  registerDataChangeListener(listener: DataChangeListener): void {
242    if (this.listeners.indexOf(listener) < 0) {
243      console.info('add listener');
244      this.listeners.push(listener);
245    }
246  }
247
248  unregisterDataChangeListener(listener: DataChangeListener): void {
249    const pos = this.listeners.indexOf(listener);
250    if (pos >= 0) {
251      console.info('remove listener');
252      this.listeners.splice(pos, 1);
253    }
254  }
255
256  notifyDataReload(): void {
257    this.listeners.forEach(listener => {
258      listener.onDataReloaded();
259    })
260  }
261
262  notifyDataAdd(index: number): void {
263    this.listeners.forEach(listener => {
264      listener.onDataAdd(index);
265    })
266  }
267
268  notifyDataChange(index: number): void {
269    this.listeners.forEach(listener => {
270      listener.onDataChange(index);
271    })
272  }
273
274  notifyDataDelete(index: number): void {
275    this.listeners.forEach(listener => {
276      listener.onDataDelete(index);
277    })
278  }
279
280  notifyDataMove(from: number, to: number): void {
281    this.listeners.forEach(listener => {
282      listener.onDataMove(from, to);
283    })
284  }
285}
286
287class MyDataSource extends BasicDataSource {
288  private dataArray: string[] = [];
289
290  public totalCount(): number {
291    return this.dataArray.length;
292  }
293
294  public getData(index: number): string {
295    return this.dataArray[index];
296  }
297
298  public addData(index: number, data: string): void {
299    this.dataArray.splice(index, 0, data);
300    this.notifyDataAdd(index);
301  }
302
303  public pushData(data: string): void {
304    this.dataArray.push(data);
305    this.notifyDataAdd(this.dataArray.length - 1);
306  }
307}
308
309@Entry
310@Component
311struct MyComponent {
312  private data: MyDataSource = new MyDataSource();
313
314  aboutToAppear() {
315    for (let i = 0; i <= 20; i++) {
316      this.data.pushData(`Hello ${i}`)
317    }
318  }
319
320  build() {
321    List({ space: 3 }) {
322      LazyForEach(this.data, (item: string) => {
323        ListItem() {
324          Row() {
325            Text(item).fontSize(50)
326              .onAppear(() => {
327                console.info("appear:" + item)
328              })
329          }.margin({ left: 10, right: 10 })
330        }
331      }, (item: string) => 'same key')
332    }.cachedCount(5)
333  }
334}
335 ```
336
337运行效果如下图所示。可以看到`Hello 0`在滑动过程中被错误渲染为`Hello 13`。
338
339**图2**  LazyForEach存在相同键值
340![LazyForEach-Render-SameKey](./figures/LazyForEach-Render-SameKey.gif)
341
342### 非首次渲染
343
344当`LazyForEach`数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用`listener`对应的接口,通知`LazyForEach`做相应的更新,各使用场景如下。
345
346- ### 添加数据
347
348```ts
349class BasicDataSource implements IDataSource {
350  private listeners: DataChangeListener[] = [];
351  private originDataArray: string[] = [];
352
353  public totalCount(): number {
354    return 0;
355  }
356
357  public getData(index: number): string {
358    return this.originDataArray[index];
359  }
360
361  registerDataChangeListener(listener: DataChangeListener): void {
362    if (this.listeners.indexOf(listener) < 0) {
363      console.info('add listener');
364      this.listeners.push(listener);
365    }
366  }
367
368  unregisterDataChangeListener(listener: DataChangeListener): void {
369    const pos = this.listeners.indexOf(listener);
370    if (pos >= 0) {
371      console.info('remove listener');
372      this.listeners.splice(pos, 1);
373    }
374  }
375
376  notifyDataReload(): void {
377    this.listeners.forEach(listener => {
378      listener.onDataReloaded();
379    })
380  }
381
382  notifyDataAdd(index: number): void {
383    this.listeners.forEach(listener => {
384      listener.onDataAdd(index);
385    })
386  }
387
388  notifyDataChange(index: number): void {
389    this.listeners.forEach(listener => {
390      listener.onDataChange(index);
391    })
392  }
393
394  notifyDataDelete(index: number): void {
395    this.listeners.forEach(listener => {
396      listener.onDataDelete(index);
397    })
398  }
399
400  notifyDataMove(from: number, to: number): void {
401    this.listeners.forEach(listener => {
402      listener.onDataMove(from, to);
403    })
404  }
405}
406
407class MyDataSource extends BasicDataSource {
408  private dataArray: string[] = [];
409
410  public totalCount(): number {
411    return this.dataArray.length;
412  }
413
414  public getData(index: number): string {
415    return this.dataArray[index];
416  }
417
418  public addData(index: number, data: string): void {
419    this.dataArray.splice(index, 0, data);
420    this.notifyDataAdd(index);
421  }
422
423  public pushData(data: string): void {
424    this.dataArray.push(data);
425    this.notifyDataAdd(this.dataArray.length - 1);
426  }
427}
428
429@Entry
430@Component
431struct MyComponent {
432  private data: MyDataSource = new MyDataSource();
433
434  aboutToAppear() {
435    for (let i = 0; i <= 20; i++) {
436      this.data.pushData(`Hello ${i}`)
437    }
438  }
439
440  build() {
441    List({ space: 3 }) {
442      LazyForEach(this.data, (item: string) => {
443        ListItem() {
444          Row() {
445            Text(item).fontSize(50)
446              .onAppear(() => {
447                console.info("appear:" + item)
448              })
449          }.margin({ left: 10, right: 10 })
450        }
451        .onClick(() => {
452          // 点击追加子组件
453          this.data.pushData(`Hello ${this.data.totalCount()}`);
454        })
455      }, (item: string) => item)
456    }.cachedCount(5)
457  }
458}
459```
460
461当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`pushData`方法,该方法会在数据源末尾添加数据并调用`notifyDataAdd`方法。在`notifyDataAdd`方法内会又调用`listener.onDataAdd`方法,该方法会通知`LazyForEach`在该处有数据添加,`LazyForEach`便会在该索引处新建子组件。
462
463运行效果如下图所示。
464
465**图3**  LazyForEach添加数据
466![LazyForEach-Add-Data](./figures/LazyForEach-Add-Data.gif)
467
468- ### 删除数据
469
470```ts
471class BasicDataSource implements IDataSource {
472  private listeners: DataChangeListener[] = [];
473  private originDataArray: string[] = [];
474
475  public totalCount(): number {
476    return 0;
477  }
478
479  public getData(index: number): string {
480    return this.originDataArray[index];
481  }
482
483  registerDataChangeListener(listener: DataChangeListener): void {
484    if (this.listeners.indexOf(listener) < 0) {
485      console.info('add listener');
486      this.listeners.push(listener);
487    }
488  }
489
490  unregisterDataChangeListener(listener: DataChangeListener): void {
491    const pos = this.listeners.indexOf(listener);
492    if (pos >= 0) {
493      console.info('remove listener');
494      this.listeners.splice(pos, 1);
495    }
496  }
497
498  notifyDataReload(): void {
499    this.listeners.forEach(listener => {
500      listener.onDataReloaded();
501    })
502  }
503
504  notifyDataAdd(index: number): void {
505    this.listeners.forEach(listener => {
506      listener.onDataAdd(index);
507    })
508  }
509
510  notifyDataChange(index: number): void {
511    this.listeners.forEach(listener => {
512      listener.onDataChange(index);
513    })
514  }
515
516  notifyDataDelete(index: number): void {
517    this.listeners.forEach(listener => {
518      listener.onDataDelete(index);
519    })
520  }
521
522  notifyDataMove(from: number, to: number): void {
523    this.listeners.forEach(listener => {
524      listener.onDataMove(from, to);
525    })
526  }
527}
528
529class MyDataSource extends BasicDataSource {
530  dataArray: string[] = [];
531
532  public totalCount(): number {
533    return this.dataArray.length;
534  }
535
536  public getData(index: number): string {
537    return this.dataArray[index];
538  }
539
540  public addData(index: number, data: string): void {
541    this.dataArray.splice(index, 0, data);
542    this.notifyDataAdd(index);
543  }
544
545  public pushData(data: string): void {
546    this.dataArray.push(data);
547    this.notifyDataAdd(this.dataArray.length - 1);
548  }
549
550  public deleteData(index: number): void {
551    this.dataArray.splice(index, 1);
552    this.notifyDataDelete(index);
553  }
554}
555
556@Entry
557@Component
558struct MyComponent {
559  private data: MyDataSource = new MyDataSource();
560
561  aboutToAppear() {
562    for (let i = 0; i <= 20; i++) {
563      this.data.pushData(`Hello ${i}`)
564    }
565  }
566
567  build() {
568    List({ space: 3 }) {
569      LazyForEach(this.data, (item: string, index: number) => {
570        ListItem() {
571          Row() {
572            Text(item).fontSize(50)
573              .onAppear(() => {
574                console.info("appear:" + item)
575              })
576          }.margin({ left: 10, right: 10 })
577        }
578        .onClick(() => {
579          // 点击删除子组件
580          this.data.deleteData(this.data.dataArray.indexOf(item));
581        })
582      }, (item: string) => item)
583    }.cachedCount(5)
584  }
585}
586```
587
588当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`deleteData`方法,该方法会删除数据源对应索引处的数据并调用`notifyDataDelete`方法。在`notifyDataDelete`方法内会又调用`listener.onDataDelete`方法,该方法会通知`LazyForEach`在该处有数据删除,`LazyForEach`便会在该索引处删除对应子组件。
589
590运行效果如下图所示。
591
592**图4**  LazyForEach删除数据
593![LazyForEach-Delete-Data](./figures/LazyForEach-Delete-Data.gif)
594
595- ### 交换数据
596
597```ts
598class BasicDataSource implements IDataSource {
599  private listeners: DataChangeListener[] = [];
600  private originDataArray: string[] = [];
601
602  public totalCount(): number {
603    return 0;
604  }
605
606  public getData(index: number): string {
607    return this.originDataArray[index];
608  }
609
610  registerDataChangeListener(listener: DataChangeListener): void {
611    if (this.listeners.indexOf(listener) < 0) {
612      console.info('add listener');
613      this.listeners.push(listener);
614    }
615  }
616
617  unregisterDataChangeListener(listener: DataChangeListener): void {
618    const pos = this.listeners.indexOf(listener);
619    if (pos >= 0) {
620      console.info('remove listener');
621      this.listeners.splice(pos, 1);
622    }
623  }
624
625  notifyDataReload(): void {
626    this.listeners.forEach(listener => {
627      listener.onDataReloaded();
628    })
629  }
630
631  notifyDataAdd(index: number): void {
632    this.listeners.forEach(listener => {
633      listener.onDataAdd(index);
634    })
635  }
636
637  notifyDataChange(index: number): void {
638    this.listeners.forEach(listener => {
639      listener.onDataChange(index);
640    })
641  }
642
643  notifyDataDelete(index: number): void {
644    this.listeners.forEach(listener => {
645      listener.onDataDelete(index);
646    })
647  }
648
649  notifyDataMove(from: number, to: number): void {
650    this.listeners.forEach(listener => {
651      listener.onDataMove(from, to);
652    })
653  }
654}
655
656class MyDataSource extends BasicDataSource {
657  dataArray: string[] = [];
658
659  public totalCount(): number {
660    return this.dataArray.length;
661  }
662
663  public getData(index: number): string {
664    return this.dataArray[index];
665  }
666
667  public addData(index: number, data: string): void {
668    this.dataArray.splice(index, 0, data);
669    this.notifyDataAdd(index);
670  }
671
672  public pushData(data: string): void {
673    this.dataArray.push(data);
674    this.notifyDataAdd(this.dataArray.length - 1);
675  }
676
677  public deleteData(index: number): void {
678    this.dataArray.splice(index, 1);
679    this.notifyDataDelete(index);
680  }
681
682  public moveData(from: number, to: number): void {
683    let temp: string = this.dataArray[from];
684    this.dataArray[from] = this.dataArray[to];
685    this.dataArray[to] = temp;
686    this.notifyDataMove(from, to);
687  }
688}
689
690@Entry
691@Component
692struct MyComponent {
693  private moved: number[] = [];
694  private data: MyDataSource = new MyDataSource();
695
696  aboutToAppear() {
697    for (let i = 0; i <= 20; i++) {
698      this.data.pushData(`Hello ${i}`)
699    }
700  }
701
702  build() {
703    List({ space: 3 }) {
704      LazyForEach(this.data, (item: string, index: number) => {
705        ListItem() {
706          Row() {
707            Text(item).fontSize(50)
708              .onAppear(() => {
709                console.info("appear:" + item)
710              })
711          }.margin({ left: 10, right: 10 })
712        }
713        .onClick(() => {
714          this.moved.push(this.data.dataArray.indexOf(item));
715          if (this.moved.length === 2) {
716          	// 点击移动子组件
717          	this.data.moveData(this.moved[0], this.moved[1]);
718            this.moved = [];
719          }
720        })
721      }, (item: string) => item)
722    }.cachedCount(5)
723  }
724}
725```
726
727当我们首次点击`LazyForEach`的子组件时,在moved成员变量内存入要移动的数据索引,再次点击`LazyForEach`另一个子组件时,我们将首次点击的子组件移到此处。调用数据源`data`的`moveData`方法,该方法会将数据源对应数据移动到预期的位置并调用`notifyDatMove`方法。在`notifyDataMove`方法内会又调用`listener.onDataMove`方法,该方法通知`LazyForEach`在该处有数据需要移动,`LazyForEach`便会将`from`和`to`索引处的子组件进行位置调换。
728
729运行效果如下图所示。
730
731**图5**  LazyForEach交换数据
732![LazyForEach-Exchange-Data](./figures/LazyForEach-Exchange-Data.gif)
733
734- ### 改变单个数据
735
736```ts
737class BasicDataSource implements IDataSource {
738  private listeners: DataChangeListener[] = [];
739  private originDataArray: string[] = [];
740
741  public totalCount(): number {
742    return 0;
743  }
744
745  public getData(index: number): string {
746    return this.originDataArray[index];
747  }
748
749  registerDataChangeListener(listener: DataChangeListener): void {
750    if (this.listeners.indexOf(listener) < 0) {
751      console.info('add listener');
752      this.listeners.push(listener);
753    }
754  }
755
756  unregisterDataChangeListener(listener: DataChangeListener): void {
757    const pos = this.listeners.indexOf(listener);
758    if (pos >= 0) {
759      console.info('remove listener');
760      this.listeners.splice(pos, 1);
761    }
762  }
763
764  notifyDataReload(): void {
765    this.listeners.forEach(listener => {
766      listener.onDataReloaded();
767    })
768  }
769
770  notifyDataAdd(index: number): void {
771    this.listeners.forEach(listener => {
772      listener.onDataAdd(index);
773    })
774  }
775
776  notifyDataChange(index: number): void {
777    this.listeners.forEach(listener => {
778      listener.onDataChange(index);
779    })
780  }
781
782  notifyDataDelete(index: number): void {
783    this.listeners.forEach(listener => {
784      listener.onDataDelete(index);
785    })
786  }
787
788  notifyDataMove(from: number, to: number): void {
789    this.listeners.forEach(listener => {
790      listener.onDataMove(from, to);
791    })
792  }
793}
794
795class MyDataSource extends BasicDataSource {
796  private dataArray: string[] = [];
797
798  public totalCount(): number {
799    return this.dataArray.length;
800  }
801
802  public getData(index: number): string {
803    return this.dataArray[index];
804  }
805
806  public addData(index: number, data: string): void {
807    this.dataArray.splice(index, 0, data);
808    this.notifyDataAdd(index);
809  }
810
811  public pushData(data: string): void {
812    this.dataArray.push(data);
813    this.notifyDataAdd(this.dataArray.length - 1);
814  }
815
816  public deleteData(index: number): void {
817    this.dataArray.splice(index, 1);
818    this.notifyDataDelete(index);
819  }
820
821  public changeData(index: number, data: string): void {
822    this.dataArray.splice(index, 1, data);
823    this.notifyDataChange(index);
824  }
825}
826
827@Entry
828@Component
829struct MyComponent {
830  private moved: number[] = [];
831  private data: MyDataSource = new MyDataSource();
832
833  aboutToAppear() {
834    for (let i = 0; i <= 20; i++) {
835      this.data.pushData(`Hello ${i}`)
836    }
837  }
838
839
840  build() {
841    List({ space: 3 }) {
842      LazyForEach(this.data, (item: string, index: number) => {
843        ListItem() {
844          Row() {
845            Text(item).fontSize(50)
846              .onAppear(() => {
847                console.info("appear:" + item)
848              })
849          }.margin({ left: 10, right: 10 })
850        }
851        .onClick(() => {
852          this.data.changeData(index, item + '00');
853        })
854      }, (item: string) => item)
855    }.cachedCount(5)
856  }
857}
858```
859
860当我们点击`LazyForEach`的子组件时,首先改变当前数据,然后调用数据源`data`的`changeData`方法,在该方法内会调用`notifyDataChange`方法。在`notifyDataChange`方法内会又调用`listener.onDataChange`方法,该方法通知`LazyForEach`组件该处有数据发生变化,`LazyForEach`便会在对应索引处重建子组件。
861
862运行效果如下图所示。
863
864**图6**  LazyForEach改变单个数据
865![LazyForEach-Change-SingleData](./figures/LazyForEach-Change-SingleData.gif)
866
867- ### 改变多个数据
868
869```ts
870class BasicDataSource implements IDataSource {
871  private listeners: DataChangeListener[] = [];
872  private originDataArray: string[] = [];
873
874  public totalCount(): number {
875    return 0;
876  }
877
878  public getData(index: number): string {
879    return this.originDataArray[index];
880  }
881
882  registerDataChangeListener(listener: DataChangeListener): void {
883    if (this.listeners.indexOf(listener) < 0) {
884      console.info('add listener');
885      this.listeners.push(listener);
886    }
887  }
888
889  unregisterDataChangeListener(listener: DataChangeListener): void {
890    const pos = this.listeners.indexOf(listener);
891    if (pos >= 0) {
892      console.info('remove listener');
893      this.listeners.splice(pos, 1);
894    }
895  }
896
897  notifyDataReload(): void {
898    this.listeners.forEach(listener => {
899      listener.onDataReloaded();
900    })
901  }
902
903  notifyDataAdd(index: number): void {
904    this.listeners.forEach(listener => {
905      listener.onDataAdd(index);
906    })
907  }
908
909  notifyDataChange(index: number): void {
910    this.listeners.forEach(listener => {
911      listener.onDataChange(index);
912    })
913  }
914
915  notifyDataDelete(index: number): void {
916    this.listeners.forEach(listener => {
917      listener.onDataDelete(index);
918    })
919  }
920
921  notifyDataMove(from: number, to: number): void {
922    this.listeners.forEach(listener => {
923      listener.onDataMove(from, to);
924    })
925  }
926}
927
928class MyDataSource extends BasicDataSource {
929  private dataArray: string[] = [];
930
931  public totalCount(): number {
932    return this.dataArray.length;
933  }
934
935  public getData(index: number): string {
936    return this.dataArray[index];
937  }
938
939  public addData(index: number, data: string): void {
940    this.dataArray.splice(index, 0, data);
941    this.notifyDataAdd(index);
942  }
943
944  public pushData(data: string): void {
945    this.dataArray.push(data);
946    this.notifyDataAdd(this.dataArray.length - 1);
947  }
948
949  public deleteData(index: number): void {
950    this.dataArray.splice(index, 1);
951    this.notifyDataDelete(index);
952  }
953
954  public changeData(index: number): void {
955    this.notifyDataChange(index);
956  }
957
958  public reloadData(): void {
959    this.notifyDataReload();
960  }
961
962  public modifyAllData(): void {
963    this.dataArray = this.dataArray.map((item: string) => {
964        return item + '0';
965    })
966  }
967}
968
969@Entry
970@Component
971struct MyComponent {
972  private moved: number[] = [];
973  private data: MyDataSource = new MyDataSource();
974
975  aboutToAppear() {
976    for (let i = 0; i <= 20; i++) {
977      this.data.pushData(`Hello ${i}`)
978    }
979  }
980
981  build() {
982    List({ space: 3 }) {
983      LazyForEach(this.data, (item: string, index: number) => {
984        ListItem() {
985          Row() {
986            Text(item).fontSize(50)
987              .onAppear(() => {
988                console.info("appear:" + item)
989              })
990          }.margin({ left: 10, right: 10 })
991        }
992        .onClick(() => {
993          this.data.modifyAllData();
994          this.data.reloadData();
995        })
996      }, (item: string) => item)
997    }.cachedCount(5)
998  }
999}
1000```
1001
1002当我们点击`LazyForEach`的子组件时,首先调用`data`的`modifyAllData`方法改变了数据源中的所有数据,然后调用数据源的`reloadData`方法,在该方法内会调用`notifyDataReload`方法。在`notifyDataReload`方法内会又调用`listener.onDataReloaded`方法,通知`LazyForEach`需要重建所有子节点。`LazyForEach`会将原所有数据项和新所有数据项一一做键值比对,若有相同键值则使用缓存,若键值不同则重新构建。
1003
1004运行效果如下图所示。
1005
1006**图7**  LazyForEach改变多个数据
1007![LazyForEach-Reload-Data](./figures/LazyForEach-Reload-Data.gif)
1008
1009- ### 改变数据子属性
1010
1011若仅靠`LazyForEach`的刷新机制,当`item`变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件结构较为复杂的情况下,靠改变键值去刷新渲染性能较低。因此框架提供了`@Observed`与@`ObjectLink`机制进行深度观测,可以做到仅刷新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种刷新方式。
1012
1013```ts
1014class BasicDataSource implements IDataSource {
1015  private listeners: DataChangeListener[] = [];
1016  private originDataArray: StringData[] = [];
1017
1018  public totalCount(): number {
1019    return 0;
1020  }
1021
1022  public getData(index: number): StringData {
1023    return this.originDataArray[index];
1024  }
1025
1026  registerDataChangeListener(listener: DataChangeListener): void {
1027    if (this.listeners.indexOf(listener) < 0) {
1028      console.info('add listener');
1029      this.listeners.push(listener);
1030    }
1031  }
1032
1033  unregisterDataChangeListener(listener: DataChangeListener): void {
1034    const pos = this.listeners.indexOf(listener);
1035    if (pos >= 0) {
1036      console.info('remove listener');
1037      this.listeners.splice(pos, 1);
1038    }
1039  }
1040
1041  notifyDataReload(): void {
1042    this.listeners.forEach(listener => {
1043      listener.onDataReloaded();
1044    })
1045  }
1046
1047  notifyDataAdd(index: number): void {
1048    this.listeners.forEach(listener => {
1049      listener.onDataAdd(index);
1050    })
1051  }
1052
1053  notifyDataChange(index: number): void {
1054    this.listeners.forEach(listener => {
1055      listener.onDataChange(index);
1056    })
1057  }
1058
1059  notifyDataDelete(index: number): void {
1060    this.listeners.forEach(listener => {
1061      listener.onDataDelete(index);
1062    })
1063  }
1064
1065  notifyDataMove(from: number, to: number): void {
1066    this.listeners.forEach(listener => {
1067      listener.onDataMove(from, to);
1068    })
1069  }
1070}
1071
1072class MyDataSource extends BasicDataSource {
1073  private dataArray: StringData[] = [];
1074
1075  public totalCount(): number {
1076    return this.dataArray.length;
1077  }
1078
1079  public getData(index: number): StringData {
1080    return this.dataArray[index];
1081  }
1082
1083  public addData(index: number, data: StringData): void {
1084    this.dataArray.splice(index, 0, data);
1085    this.notifyDataAdd(index);
1086  }
1087
1088  public pushData(data: StringData): void {
1089    this.dataArray.push(data);
1090    this.notifyDataAdd(this.dataArray.length - 1);
1091  }
1092}
1093
1094@Observed
1095class StringData {
1096  message: string;
1097  constructor(message: string) {
1098    this.message = message;
1099  }
1100}
1101
1102@Entry
1103@Component
1104struct MyComponent {
1105  private moved: number[] = [];
1106  @State data: MyDataSource = new MyDataSource();
1107
1108  aboutToAppear() {
1109    for (let i = 0; i <= 20; i++) {
1110      this.data.pushData(new StringData(`Hello ${i}`));
1111    }
1112  }
1113
1114  build() {
1115    List({ space: 3 }) {
1116      LazyForEach(this.data, (item: StringData, index: number) => {
1117        ListItem() {
1118          ChildComponent({data: item})
1119        }
1120        .onClick(() => {
1121          item.message += '0';
1122        })
1123      }, (item: StringData, index: number) => index.toString())
1124    }.cachedCount(5)
1125  }
1126}
1127
1128@Component
1129struct ChildComponent {
1130  @ObjectLink data: StringData
1131  build() {
1132    Row() {
1133      Text(this.data.message).fontSize(50)
1134        .onAppear(() => {
1135          console.info("appear:" + this.data.message)
1136        })
1137    }.margin({ left: 10, right: 10 })
1138  }
1139}
1140```
1141
1142此时点击`LazyForEach`子组件改变`item.message`时,重渲染依赖的是`ChildComponent`的`@ObjectLink`成员变量对其子属性的监听,此时框架只会刷新`Text(this.data.message)`,不会去重建整个`ListItem`子组件。
1143
1144**图8**  LazyForEach改变数据子属性
1145![LazyForEach-Change-SubProperty](./figures/LazyForEach-Change-SubProperty.gif)
1146
1147## 常见使用问题
1148
1149- ### 渲染结果非预期
1150
1151  ```ts
1152  class BasicDataSource implements IDataSource {
1153    private listeners: DataChangeListener[] = [];
1154    private originDataArray: string[] = [];
1155
1156    public totalCount(): number {
1157      return 0;
1158    }
1159
1160    public getData(index: number): string {
1161      return this.originDataArray[index];
1162    }
1163
1164    registerDataChangeListener(listener: DataChangeListener): void {
1165      if (this.listeners.indexOf(listener) < 0) {
1166        console.info('add listener');
1167        this.listeners.push(listener);
1168      }
1169    }
1170
1171    unregisterDataChangeListener(listener: DataChangeListener): void {
1172      const pos = this.listeners.indexOf(listener);
1173      if (pos >= 0) {
1174        console.info('remove listener');
1175        this.listeners.splice(pos, 1);
1176      }
1177    }
1178
1179    notifyDataReload(): void {
1180      this.listeners.forEach(listener => {
1181        listener.onDataReloaded();
1182      })
1183    }
1184
1185    notifyDataAdd(index: number): void {
1186      this.listeners.forEach(listener => {
1187        listener.onDataAdd(index);
1188      })
1189    }
1190
1191    notifyDataChange(index: number): void {
1192      this.listeners.forEach(listener => {
1193        listener.onDataChange(index);
1194      })
1195    }
1196
1197    notifyDataDelete(index: number): void {
1198      this.listeners.forEach(listener => {
1199        listener.onDataDelete(index);
1200      })
1201    }
1202
1203    notifyDataMove(from: number, to: number): void {
1204      this.listeners.forEach(listener => {
1205        listener.onDataMove(from, to);
1206      })
1207    }
1208  }
1209
1210  class MyDataSource extends BasicDataSource {
1211    private dataArray: string[] = [];
1212
1213    public totalCount(): number {
1214      return this.dataArray.length;
1215    }
1216
1217    public getData(index: number): string {
1218      return this.dataArray[index];
1219    }
1220
1221    public addData(index: number, data: string): void {
1222      this.dataArray.splice(index, 0, data);
1223      this.notifyDataAdd(index);
1224    }
1225
1226    public pushData(data: string): void {
1227      this.dataArray.push(data);
1228      this.notifyDataAdd(this.dataArray.length - 1);
1229    }
1230
1231    public deleteData(index: number): void {
1232      this.dataArray.splice(index, 1);
1233      this.notifyDataDelete(index);
1234    }
1235  }
1236
1237  @Entry
1238  @Component
1239  struct MyComponent {
1240    private data: MyDataSource = new MyDataSource();
1241
1242    aboutToAppear() {
1243      for (let i = 0; i <= 20; i++) {
1244        this.data.pushData(`Hello ${i}`)
1245      }
1246    }
1247
1248    build() {
1249      List({ space: 3 }) {
1250        LazyForEach(this.data, (item: string, index: number) => {
1251          ListItem() {
1252            Row() {
1253              Text(item).fontSize(50)
1254                .onAppear(() => {
1255                  console.info("appear:" + item)
1256                })
1257            }.margin({ left: 10, right: 10 })
1258          }
1259          .onClick(() => {
1260            // 点击删除子组件
1261            this.data.deleteData(index);
1262          })
1263        }, (item: string) => item)
1264      }.cachedCount(5)
1265    }
1266  }
1267  ```
1268
1269  **图9**  LazyForEach删除数据非预期
1270  ![LazyForEach-Render-Not-Expected](./figures/LazyForEach-Render-Not-Expected.gif)
1271
1272  当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其`index`均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的`index`,其`itemGenerator`中的`index`并没有发生变化,所以删除结果和预期不符。
1273
1274  修复代码如下所示。
1275
1276  ```ts
1277  class BasicDataSource implements IDataSource {
1278    private listeners: DataChangeListener[] = [];
1279    private originDataArray: string[] = [];
1280
1281    public totalCount(): number {
1282      return 0;
1283    }
1284
1285    public getData(index: number): string {
1286      return this.originDataArray[index];
1287    }
1288
1289    registerDataChangeListener(listener: DataChangeListener): void {
1290      if (this.listeners.indexOf(listener) < 0) {
1291        console.info('add listener');
1292        this.listeners.push(listener);
1293      }
1294    }
1295
1296    unregisterDataChangeListener(listener: DataChangeListener): void {
1297      const pos = this.listeners.indexOf(listener);
1298      if (pos >= 0) {
1299        console.info('remove listener');
1300        this.listeners.splice(pos, 1);
1301      }
1302    }
1303
1304    notifyDataReload(): void {
1305      this.listeners.forEach(listener => {
1306        listener.onDataReloaded();
1307      })
1308    }
1309
1310    notifyDataAdd(index: number): void {
1311      this.listeners.forEach(listener => {
1312        listener.onDataAdd(index);
1313      })
1314    }
1315
1316    notifyDataChange(index: number): void {
1317      this.listeners.forEach(listener => {
1318        listener.onDataChange(index);
1319      })
1320    }
1321
1322    notifyDataDelete(index: number): void {
1323      this.listeners.forEach(listener => {
1324        listener.onDataDelete(index);
1325      })
1326    }
1327
1328    notifyDataMove(from: number, to: number): void {
1329      this.listeners.forEach(listener => {
1330        listener.onDataMove(from, to);
1331      })
1332    }
1333  }
1334
1335  class MyDataSource extends BasicDataSource {
1336    private dataArray: string[] = [];
1337
1338    public totalCount(): number {
1339      return this.dataArray.length;
1340    }
1341
1342    public getData(index: number): string {
1343      return this.dataArray[index];
1344    }
1345
1346    public addData(index: number, data: string): void {
1347      this.dataArray.splice(index, 0, data);
1348      this.notifyDataAdd(index);
1349    }
1350
1351    public pushData(data: string): void {
1352      this.dataArray.push(data);
1353      this.notifyDataAdd(this.dataArray.length - 1);
1354    }
1355
1356    public deleteData(index: number): void {
1357      this.dataArray.splice(index, 1);
1358      this.notifyDataDelete(index);
1359    }
1360
1361    public reloadData(): void {
1362      this.notifyDataReload();
1363    }
1364  }
1365
1366  @Entry
1367  @Component
1368  struct MyComponent {
1369    private data: MyDataSource = new MyDataSource();
1370
1371    aboutToAppear() {
1372      for (let i = 0; i <= 20; i++) {
1373        this.data.pushData(`Hello ${i}`)
1374      }
1375    }
1376
1377    build() {
1378      List({ space: 3 }) {
1379        LazyForEach(this.data, (item: string, index: number) => {
1380          ListItem() {
1381            Row() {
1382              Text(item).fontSize(50)
1383                .onAppear(() => {
1384                  console.info("appear:" + item)
1385                })
1386            }.margin({ left: 10, right: 10 })
1387          }
1388          .onClick(() => {
1389            // 点击删除子组件
1390            this.data.deleteData(index);
1391            // 重置所有子组件的index索引
1392            this.data.reloadData();
1393          })
1394        }, (item: string, index: number) => item + index.toString())
1395      }.cachedCount(5)
1396    }
1397  }
1398  ```
1399
1400  在删除一个数据项后调用`reloadData`方法,重建后面的数据项,以达到更新`index`索引的目的。
1401
1402  **图10**  修复LazyForEach删除数据非预期
1403  ![LazyForEach-Render-Not-Expected-Repair](./figures/LazyForEach-Render-Not-Expected-Repair.gif)
1404
1405- ### 重渲染时图片闪烁
1406
1407  ```ts
1408  class BasicDataSource implements IDataSource {
1409    private listeners: DataChangeListener[] = [];
1410    private originDataArray: StringData[] = [];
1411
1412    public totalCount(): number {
1413      return 0;
1414    }
1415
1416    public getData(index: number): StringData {
1417      return this.originDataArray[index];
1418    }
1419
1420    registerDataChangeListener(listener: DataChangeListener): void {
1421      if (this.listeners.indexOf(listener) < 0) {
1422        console.info('add listener');
1423        this.listeners.push(listener);
1424      }
1425    }
1426
1427    unregisterDataChangeListener(listener: DataChangeListener): void {
1428      const pos = this.listeners.indexOf(listener);
1429      if (pos >= 0) {
1430        console.info('remove listener');
1431        this.listeners.splice(pos, 1);
1432      }
1433    }
1434
1435    notifyDataReload(): void {
1436      this.listeners.forEach(listener => {
1437        listener.onDataReloaded();
1438      })
1439    }
1440
1441    notifyDataAdd(index: number): void {
1442      this.listeners.forEach(listener => {
1443        listener.onDataAdd(index);
1444      })
1445    }
1446
1447    notifyDataChange(index: number): void {
1448      this.listeners.forEach(listener => {
1449        listener.onDataChange(index);
1450      })
1451    }
1452
1453    notifyDataDelete(index: number): void {
1454      this.listeners.forEach(listener => {
1455        listener.onDataDelete(index);
1456      })
1457    }
1458
1459    notifyDataMove(from: number, to: number): void {
1460      this.listeners.forEach(listener => {
1461        listener.onDataMove(from, to);
1462      })
1463    }
1464  }
1465
1466  class MyDataSource extends BasicDataSource {
1467    private dataArray: StringData[] = [];
1468
1469    public totalCount(): number {
1470      return this.dataArray.length;
1471    }
1472
1473    public getData(index: number): StringData {
1474      return this.dataArray[index];
1475    }
1476
1477    public addData(index: number, data: StringData): void {
1478      this.dataArray.splice(index, 0, data);
1479      this.notifyDataAdd(index);
1480    }
1481
1482    public pushData(data: StringData): void {
1483      this.dataArray.push(data);
1484      this.notifyDataAdd(this.dataArray.length - 1);
1485    }
1486
1487    public reloadData(): void {
1488      this.notifyDataReload();
1489    }
1490  }
1491
1492  class StringData {
1493    message: string;
1494    imgSrc: Resource;
1495    constructor(message: string, imgSrc: Resource) {
1496        this.message = message;
1497        this.imgSrc = imgSrc;
1498    }
1499  }
1500
1501  @Entry
1502  @Component
1503  struct MyComponent {
1504    private moved: number[] = [];
1505    private data: MyDataSource = new MyDataSource();
1506
1507    aboutToAppear() {
1508      for (let i = 0; i <= 20; i++) {
1509        this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
1510      }
1511    }
1512
1513    build() {
1514      List({ space: 3 }) {
1515        LazyForEach(this.data, (item: StringData, index: number) => {
1516          ListItem() {
1517            Column() {
1518              Text(item.message).fontSize(50)
1519                .onAppear(() => {
1520                  console.info("appear:" + item.message)
1521                })
1522              Image(item.imgSrc)
1523                .width(500)
1524                .height(200)
1525            }.margin({ left: 10, right: 10 })
1526          }
1527          .onClick(() => {
1528            item.message += '00';
1529            this.data.reloadData();
1530          })
1531        }, (item: StringData, index: number) => JSON.stringify(item))
1532      }.cachedCount(5)
1533    }
1534  }
1535  ```
1536
1537  **图11**  LazyForEach仅改变文字但是图片闪烁问题
1538  ![LazyForEach-Image-Flush](./figures/LazyForEach-Image-Flush.gif)
1539
1540  在我们点击`ListItem`子组件时,我们只改变了数据项的`message`属性,但是`LazyForEach`的刷新机制会导致整个`ListItem`被重建。由于`Image`组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用`@ObjectLink`和`@Observed`去单独刷新使用了`item.message`的`Text`组件。
1541
1542  修复代码如下所示。
1543
1544  ```ts
1545  class BasicDataSource implements IDataSource {
1546    private listeners: DataChangeListener[] = [];
1547    private originDataArray: StringData[] = [];
1548
1549    public totalCount(): number {
1550      return 0;
1551    }
1552
1553    public getData(index: number): StringData {
1554      return this.originDataArray[index];
1555    }
1556
1557    registerDataChangeListener(listener: DataChangeListener): void {
1558      if (this.listeners.indexOf(listener) < 0) {
1559        console.info('add listener');
1560        this.listeners.push(listener);
1561      }
1562    }
1563
1564    unregisterDataChangeListener(listener: DataChangeListener): void {
1565      const pos = this.listeners.indexOf(listener);
1566      if (pos >= 0) {
1567        console.info('remove listener');
1568        this.listeners.splice(pos, 1);
1569      }
1570    }
1571
1572    notifyDataReload(): void {
1573      this.listeners.forEach(listener => {
1574        listener.onDataReloaded();
1575      })
1576    }
1577
1578    notifyDataAdd(index: number): void {
1579      this.listeners.forEach(listener => {
1580        listener.onDataAdd(index);
1581      })
1582    }
1583
1584    notifyDataChange(index: number): void {
1585      this.listeners.forEach(listener => {
1586        listener.onDataChange(index);
1587      })
1588    }
1589
1590    notifyDataDelete(index: number): void {
1591      this.listeners.forEach(listener => {
1592        listener.onDataDelete(index);
1593      })
1594    }
1595
1596    notifyDataMove(from: number, to: number): void {
1597      this.listeners.forEach(listener => {
1598        listener.onDataMove(from, to);
1599      })
1600    }
1601  }
1602
1603  class MyDataSource extends BasicDataSource {
1604    private dataArray: StringData[] = [];
1605
1606    public totalCount(): number {
1607      return this.dataArray.length;
1608    }
1609
1610    public getData(index: number): StringData {
1611      return this.dataArray[index];
1612    }
1613
1614    public addData(index: number, data: StringData): void {
1615      this.dataArray.splice(index, 0, data);
1616      this.notifyDataAdd(index);
1617    }
1618
1619    public pushData(data: StringData): void {
1620      this.dataArray.push(data);
1621      this.notifyDataAdd(this.dataArray.length - 1);
1622    }
1623  }
1624
1625  @Observed
1626  class StringData {
1627    message: string;
1628    imgSrc: Resource;
1629    constructor(message: string, imgSrc: Resource) {
1630        this.message = message;
1631        this.imgSrc = imgSrc;
1632    }
1633  }
1634
1635  @Entry
1636  @Component
1637  struct MyComponent {
1638    @State data: MyDataSource = new MyDataSource();
1639
1640    aboutToAppear() {
1641      for (let i = 0; i <= 20; i++) {
1642        this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
1643      }
1644    }
1645
1646    build() {
1647      List({ space: 3 }) {
1648        LazyForEach(this.data, (item: StringData, index: number) => {
1649          ListItem() {
1650            ChildComponent({data: item})
1651          }
1652          .onClick(() => {
1653            item.message += '0';
1654          })
1655        }, (item: StringData, index: number) => index.toString())
1656      }.cachedCount(5)
1657    }
1658  }
1659
1660  @Component
1661  struct ChildComponent {
1662    @ObjectLink data: StringData
1663    build() {
1664      Column() {
1665        Text(this.data.message).fontSize(50)
1666          .onAppear(() => {
1667            console.info("appear:" + this.data.message)
1668          })
1669        Image(this.data.imgSrc)
1670          .width(500)
1671          .height(200)
1672      }.margin({ left: 10, right: 10 })
1673    }
1674  }
1675  ```
1676
1677  **图12**  修复LazyForEach仅改变文字但是图片闪烁问题
1678  ![LazyForEach-Image-Flush-Repair](./figures/LazyForEach-Image-Flush-Repair.gif)
1679
1680- ### @ObjectLink属性变化UI未更新
1681
1682  ```ts
1683  class BasicDataSource implements IDataSource {
1684    private listeners: DataChangeListener[] = [];
1685    private originDataArray: StringData[] = [];
1686
1687    public totalCount(): number {
1688      return 0;
1689    }
1690
1691    public getData(index: number): StringData {
1692      return this.originDataArray[index];
1693    }
1694
1695    registerDataChangeListener(listener: DataChangeListener): void {
1696      if (this.listeners.indexOf(listener) < 0) {
1697        console.info('add listener');
1698        this.listeners.push(listener);
1699      }
1700    }
1701
1702    unregisterDataChangeListener(listener: DataChangeListener): void {
1703      const pos = this.listeners.indexOf(listener);
1704      if (pos >= 0) {
1705        console.info('remove listener');
1706        this.listeners.splice(pos, 1);
1707      }
1708    }
1709
1710    notifyDataReload(): void {
1711      this.listeners.forEach(listener => {
1712        listener.onDataReloaded();
1713      })
1714    }
1715
1716    notifyDataAdd(index: number): void {
1717      this.listeners.forEach(listener => {
1718        listener.onDataAdd(index);
1719      })
1720    }
1721
1722    notifyDataChange(index: number): void {
1723      this.listeners.forEach(listener => {
1724        listener.onDataChange(index);
1725      })
1726    }
1727
1728    notifyDataDelete(index: number): void {
1729      this.listeners.forEach(listener => {
1730        listener.onDataDelete(index);
1731      })
1732    }
1733
1734    notifyDataMove(from: number, to: number): void {
1735      this.listeners.forEach(listener => {
1736        listener.onDataMove(from, to);
1737      })
1738    }
1739  }
1740
1741  class MyDataSource extends BasicDataSource {
1742    private dataArray: StringData[] = [];
1743
1744    public totalCount(): number {
1745      return this.dataArray.length;
1746    }
1747
1748    public getData(index: number): StringData {
1749      return this.dataArray[index];
1750    }
1751
1752    public addData(index: number, data: StringData): void {
1753      this.dataArray.splice(index, 0, data);
1754      this.notifyDataAdd(index);
1755    }
1756
1757    public pushData(data: StringData): void {
1758      this.dataArray.push(data);
1759      this.notifyDataAdd(this.dataArray.length - 1);
1760    }
1761  }
1762
1763  @Observed
1764  class StringData {
1765    message: NestedString;
1766    constructor(message: NestedString) {
1767      this.message = message;
1768    }
1769  }
1770
1771  @Observed
1772  class NestedString {
1773    message: string;
1774    constructor(message: string) {
1775      this.message = message;
1776    }
1777  }
1778
1779  @Entry
1780  @Component
1781  struct MyComponent {
1782    private moved: number[] = [];
1783    @State data: MyDataSource = new MyDataSource();
1784
1785    aboutToAppear() {
1786      for (let i = 0; i <= 20; i++) {
1787        this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
1788      }
1789    }
1790
1791    build() {
1792      List({ space: 3 }) {
1793        LazyForEach(this.data, (item: StringData, index: number) => {
1794          ListItem() {
1795            ChildComponent({data: item})
1796          }
1797          .onClick(() => {
1798            item.message.message += '0';
1799          })
1800        }, (item: StringData, index: number) => JSON.stringify(item) + index.toString())
1801      }.cachedCount(5)
1802    }
1803  }
1804
1805  @Component
1806  struct ChildComponent {
1807    @ObjectLink data: StringData
1808    build() {
1809      Row() {
1810        Text(this.data.message.message).fontSize(50)
1811          .onAppear(() => {
1812            console.info("appear:" + this.data.message.message)
1813          })
1814      }.margin({ left: 10, right: 10 })
1815    }
1816  }
1817  ```
1818
1819  **图13**  ObjectLink属性变化后UI未更新
1820  ![LazyForEach-ObjectLink-NotRenderUI](./figures/LazyForEach-ObjectLink-NotRenderUI.gif)
1821
1822  @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体[请查看@ObjectLink与@Observed的详细使用方法和限制条件](./arkts-observed-and-objectlink.md)。
1823
1824  修复代码如下所示。
1825
1826  ```ts
1827  class BasicDataSource implements IDataSource {
1828    private listeners: DataChangeListener[] = [];
1829    private originDataArray: StringData[] = [];
1830
1831    public totalCount(): number {
1832      return 0;
1833    }
1834
1835    public getData(index: number): StringData {
1836      return this.originDataArray[index];
1837    }
1838
1839    registerDataChangeListener(listener: DataChangeListener): void {
1840      if (this.listeners.indexOf(listener) < 0) {
1841        console.info('add listener');
1842        this.listeners.push(listener);
1843      }
1844    }
1845
1846    unregisterDataChangeListener(listener: DataChangeListener): void {
1847      const pos = this.listeners.indexOf(listener);
1848      if (pos >= 0) {
1849        console.info('remove listener');
1850        this.listeners.splice(pos, 1);
1851      }
1852    }
1853
1854    notifyDataReload(): void {
1855      this.listeners.forEach(listener => {
1856        listener.onDataReloaded();
1857      })
1858    }
1859
1860    notifyDataAdd(index: number): void {
1861      this.listeners.forEach(listener => {
1862        listener.onDataAdd(index);
1863      })
1864    }
1865
1866    notifyDataChange(index: number): void {
1867      this.listeners.forEach(listener => {
1868        listener.onDataChange(index);
1869      })
1870    }
1871
1872    notifyDataDelete(index: number): void {
1873      this.listeners.forEach(listener => {
1874        listener.onDataDelete(index);
1875      })
1876    }
1877
1878    notifyDataMove(from: number, to: number): void {
1879      this.listeners.forEach(listener => {
1880        listener.onDataMove(from, to);
1881      })
1882    }
1883  }
1884
1885  class MyDataSource extends BasicDataSource {
1886    private dataArray: StringData[] = [];
1887
1888    public totalCount(): number {
1889      return this.dataArray.length;
1890    }
1891
1892    public getData(index: number): StringData {
1893      return this.dataArray[index];
1894    }
1895
1896    public addData(index: number, data: StringData): void {
1897      this.dataArray.splice(index, 0, data);
1898      this.notifyDataAdd(index);
1899    }
1900
1901    public pushData(data: StringData): void {
1902      this.dataArray.push(data);
1903      this.notifyDataAdd(this.dataArray.length - 1);
1904    }
1905  }
1906
1907  @Observed
1908  class StringData {
1909    message: NestedString;
1910    constructor(message: NestedString) {
1911      this.message = message;
1912    }
1913  }
1914
1915  @Observed
1916  class NestedString {
1917    message: string;
1918    constructor(message: string) {
1919      this.message = message;
1920    }
1921  }
1922
1923  @Entry
1924  @Component
1925  struct MyComponent {
1926    private moved: number[] = [];
1927    @State data: MyDataSource = new MyDataSource();
1928
1929    aboutToAppear() {
1930      for (let i = 0; i <= 20; i++) {
1931        this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
1932      }
1933    }
1934
1935    build() {
1936      List({ space: 3 }) {
1937        LazyForEach(this.data, (item: StringData, index: number) => {
1938          ListItem() {
1939            ChildComponent({data: item})
1940          }
1941          .onClick(() => {
1942            item.message = new NestedString(item.message.message + '0');
1943          })
1944        }, (item: StringData, index: number) => JSON.stringify(item) + index.toString())
1945      }.cachedCount(5)
1946    }
1947  }
1948
1949  @Component
1950  struct ChildComponent {
1951    @ObjectLink data: StringData
1952    build() {
1953      Row() {
1954        Text(this.data.message.message).fontSize(50)
1955          .onAppear(() => {
1956            console.info("appear:" + this.data.message.message)
1957          })
1958      }.margin({ left: 10, right: 10 })
1959    }
1960  }
1961  ```
1962
1963  **图14**  修复ObjectLink属性变化后UI更新
1964  ![LazyForEach-ObjectLink-NotRenderUI-Repair](./figures/LazyForEach-ObjectLink-NotRenderUI-Repair.gif)
1965