• 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/>**说明:**<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/arkui-ts/ts-container-list.md)、[Grid](../reference/arkui-ts/ts-container-grid.md)、[Swiper](../reference/arkui-ts/ts-container-swiper.md)以及[WaterFlow](../reference/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
160class MyDataSource extends BasicDataSource {
161  private dataArray: string[] = [];
162
163  public totalCount(): number {
164    return this.dataArray.length;
165  }
166
167  public getData(index: number): string {
168    return this.dataArray[index];
169  }
170
171  public addData(index: number, data: string): void {
172    this.dataArray.splice(index, 0, data);
173    this.notifyDataAdd(index);
174  }
175
176  public pushData(data: string): void {
177    this.dataArray.push(data);
178    this.notifyDataAdd(this.dataArray.length - 1);
179  }
180}
181
182@Entry
183@Component
184struct MyComponent {
185  private data: MyDataSource = new MyDataSource();
186
187  aboutToAppear() {
188    for (let i = 0; i <= 20; i++) {
189      this.data.pushData(`Hello ${i}`)
190    }
191  }
192
193  build() {
194    List({ space: 3 }) {
195      LazyForEach(this.data, (item: string) => {
196        ListItem() {
197          Row() {
198            Text(item).fontSize(50)
199              .onAppear(() => {
200                console.info("appear:" + item)
201              })
202          }.margin({ left: 10, right: 10 })
203        }
204      }, (item: string) => item)
205    }.cachedCount(5)
206  }
207}
208```
209
210在上述代码中,键值生成规则是`keyGenerator`函数的返回值`item`。在`LazyForEach`循环渲染时,其为数据源数组项依次生成键值`Hello 0`、`Hello 1` ... `Hello 20`,并创建对应的`ListItem`子组件渲染到界面上。
211
212运行效果如下图所示。
213
214**图1**  LazyForEach正常首次渲染
215![LazyForEach-Render-DifferentKey](./figures/LazyForEach-Render-DifferentKey.gif)
216
217- ### 键值相同时错误渲染
218
219当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,`LazyForEach`渲染的数据项键值均相同,在滑动过程中,`LazyForEach`会对划入划出当前页面的子组件进行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架可能存在取用缓存错误的情况,导致子组件渲染有问题。
220
221 ```ts
222class BasicDataSource implements IDataSource {
223  private listeners: DataChangeListener[] = [];
224  private originDataArray: string[] = [];
225
226  public totalCount(): number {
227    return 0;
228  }
229
230  public getData(index: number): string {
231    return this.originDataArray[index];
232  }
233
234  registerDataChangeListener(listener: DataChangeListener): void {
235    if (this.listeners.indexOf(listener) < 0) {
236      console.info('add listener');
237      this.listeners.push(listener);
238    }
239  }
240
241  unregisterDataChangeListener(listener: DataChangeListener): void {
242    const pos = this.listeners.indexOf(listener);
243    if (pos >= 0) {
244      console.info('remove listener');
245      this.listeners.splice(pos, 1);
246    }
247  }
248
249  notifyDataReload(): void {
250    this.listeners.forEach(listener => {
251      listener.onDataReloaded();
252    })
253  }
254
255  notifyDataAdd(index: number): void {
256    this.listeners.forEach(listener => {
257      listener.onDataAdd(index);
258    })
259  }
260
261  notifyDataChange(index: number): void {
262    this.listeners.forEach(listener => {
263      listener.onDataChange(index);
264    })
265  }
266
267  notifyDataDelete(index: number): void {
268    this.listeners.forEach(listener => {
269      listener.onDataDelete(index);
270    })
271  }
272}
273
274class MyDataSource extends BasicDataSource {
275  private dataArray: string[] = [];
276
277  public totalCount(): number {
278    return this.dataArray.length;
279  }
280
281  public getData(index: number): string {
282    return this.dataArray[index];
283  }
284
285  public addData(index: number, data: string): void {
286    this.dataArray.splice(index, 0, data);
287    this.notifyDataAdd(index);
288  }
289
290  public pushData(data: string): void {
291    this.dataArray.push(data);
292    this.notifyDataAdd(this.dataArray.length - 1);
293  }
294}
295
296@Entry
297@Component
298struct MyComponent {
299  private data: MyDataSource = new MyDataSource();
300
301  aboutToAppear() {
302    for (let i = 0; i <= 20; i++) {
303      this.data.pushData(`Hello ${i}`)
304    }
305  }
306
307  build() {
308    List({ space: 3 }) {
309      LazyForEach(this.data, (item: string) => {
310        ListItem() {
311          Row() {
312            Text(item).fontSize(50)
313              .onAppear(() => {
314                console.info("appear:" + item)
315              })
316          }.margin({ left: 10, right: 10 })
317        }
318      }, (item: string) => 'same key')
319    }.cachedCount(5)
320  }
321}
322 ```
323
324运行效果如下图所示。可以看到`Hello 0`在滑动过程中被错误渲染为`Hello 13`。
325
326**图2**  LazyForEach存在相同键值
327![LazyForEach-Render-SameKey](./figures/LazyForEach-Render-SameKey.gif)
328
329### 非首次渲染
330
331当`LazyForEach`数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用`listener`对应的接口,通知`LazyForEach`做相应的更新,各使用场景如下。
332
333- ### 添加数据
334
335```ts
336class BasicDataSource implements IDataSource {
337  private listeners: DataChangeListener[] = [];
338  private originDataArray: string[] = [];
339
340  public totalCount(): number {
341    return 0;
342  }
343
344  public getData(index: number): string {
345    return this.originDataArray[index];
346  }
347
348  registerDataChangeListener(listener: DataChangeListener): void {
349    if (this.listeners.indexOf(listener) < 0) {
350      console.info('add listener');
351      this.listeners.push(listener);
352    }
353  }
354
355  unregisterDataChangeListener(listener: DataChangeListener): void {
356    const pos = this.listeners.indexOf(listener);
357    if (pos >= 0) {
358      console.info('remove listener');
359      this.listeners.splice(pos, 1);
360    }
361  }
362
363  notifyDataReload(): void {
364    this.listeners.forEach(listener => {
365      listener.onDataReloaded();
366    })
367  }
368
369  notifyDataAdd(index: number): void {
370    this.listeners.forEach(listener => {
371      listener.onDataAdd(index);
372    })
373  }
374
375  notifyDataChange(index: number): void {
376    this.listeners.forEach(listener => {
377      listener.onDataChange(index);
378    })
379  }
380
381  notifyDataDelete(index: number): void {
382    this.listeners.forEach(listener => {
383      listener.onDataDelete(index);
384    })
385  }
386}
387
388class MyDataSource extends BasicDataSource {
389  private dataArray: string[] = [];
390
391  public totalCount(): number {
392    return this.dataArray.length;
393  }
394
395  public getData(index: number): string {
396    return this.dataArray[index];
397  }
398
399  public addData(index: number, data: string): void {
400    this.dataArray.splice(index, 0, data);
401    this.notifyDataAdd(index);
402  }
403
404  public pushData(data: string): void {
405    this.dataArray.push(data);
406    this.notifyDataAdd(this.dataArray.length - 1);
407  }
408}
409
410@Entry
411@Component
412struct MyComponent {
413  private data: MyDataSource = new MyDataSource();
414
415  aboutToAppear() {
416    for (let i = 0; i <= 20; i++) {
417      this.data.pushData(`Hello ${i}`)
418    }
419  }
420
421  build() {
422    List({ space: 3 }) {
423      LazyForEach(this.data, (item: string) => {
424        ListItem() {
425          Row() {
426            Text(item).fontSize(50)
427              .onAppear(() => {
428                console.info("appear:" + item)
429              })
430          }.margin({ left: 10, right: 10 })
431        }
432        .onClick(() => {
433          // 点击追加子组件
434          this.data.pushData(`Hello ${this.data.totalCount()}`);
435        })
436      }, (item: string) => item)
437    }.cachedCount(5)
438  }
439}
440```
441
442当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`pushData`方法,该方法会在数据源末尾添加数据并调用`notifyDataAdd`方法。在`notifyDataAdd`方法内会又调用`listener.onDataAdd`方法,该方法会通知`LazyForEach`在该处有数据添加,`LazyForEach`便会在该索引处新建子组件。
443
444运行效果如下图所示。
445
446**图3**  LazyForEach添加数据
447![LazyForEach-Add-Data](./figures/LazyForEach-Add-Data.gif)
448
449- ### 删除数据
450
451```ts
452class BasicDataSource implements IDataSource {
453  private listeners: DataChangeListener[] = [];
454  private originDataArray: string[] = [];
455
456  public totalCount(): number {
457    return 0;
458  }
459
460  public getData(index: number): string {
461    return this.originDataArray[index];
462  }
463
464  registerDataChangeListener(listener: DataChangeListener): void {
465    if (this.listeners.indexOf(listener) < 0) {
466      console.info('add listener');
467      this.listeners.push(listener);
468    }
469  }
470
471  unregisterDataChangeListener(listener: DataChangeListener): void {
472    const pos = this.listeners.indexOf(listener);
473    if (pos >= 0) {
474      console.info('remove listener');
475      this.listeners.splice(pos, 1);
476    }
477  }
478
479  notifyDataReload(): void {
480    this.listeners.forEach(listener => {
481      listener.onDataReloaded();
482    })
483  }
484
485  notifyDataAdd(index: number): void {
486    this.listeners.forEach(listener => {
487      listener.onDataAdd(index);
488    })
489  }
490
491  notifyDataChange(index: number): void {
492    this.listeners.forEach(listener => {
493      listener.onDataChange(index);
494    })
495  }
496
497  notifyDataDelete(index: number): void {
498    this.listeners.forEach(listener => {
499      listener.onDataDelete(index);
500    })
501  }
502}
503
504class MyDataSource extends BasicDataSource {
505  dataArray: string[] = [];
506
507  public totalCount(): number {
508    return this.dataArray.length;
509  }
510
511  public getData(index: number): string {
512    return this.dataArray[index];
513  }
514
515  public addData(index: number, data: string): void {
516    this.dataArray.splice(index, 0, data);
517    this.notifyDataAdd(index);
518  }
519
520  public pushData(data: string): void {
521    this.dataArray.push(data);
522    this.notifyDataAdd(this.dataArray.length - 1);
523  }
524
525  public deleteData(index: number): void {
526    this.dataArray.splice(index, 1);
527    this.notifyDataDelete(index);
528  }
529}
530
531@Entry
532@Component
533struct MyComponent {
534  private data: MyDataSource = new MyDataSource();
535
536  aboutToAppear() {
537    for (let i = 0; i <= 20; i++) {
538      this.data.pushData(`Hello ${i}`)
539    }
540  }
541
542  build() {
543    List({ space: 3 }) {
544      LazyForEach(this.data, (item: string, index: number) => {
545        ListItem() {
546          Row() {
547            Text(item).fontSize(50)
548              .onAppear(() => {
549                console.info("appear:" + item)
550              })
551          }.margin({ left: 10, right: 10 })
552        }
553        .onClick(() => {
554          // 点击删除子组件
555          this.data.deleteData(this.data.dataArray.indexOf(item));
556        })
557      }, (item: string) => item)
558    }.cachedCount(5)
559  }
560}
561```
562
563当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`deleteData`方法,该方法会删除数据源对应索引处的数据并调用`notifyDataDelete`方法。在`notifyDataDelete`方法内会又调用`listener.onDataDelete`方法,该方法会通知`LazyForEach`在该处有数据删除,`LazyForEach`便会在该索引处删除对应子组件。
564
565运行效果如下图所示。
566
567**图4**  LazyForEach删除数据
568![LazyForEach-Delete-Data](./figures/LazyForEach-Delete-Data.gif)
569
570- ### 改变单个数据
571
572```ts
573class BasicDataSource implements IDataSource {
574  private listeners: DataChangeListener[] = [];
575  private originDataArray: string[] = [];
576
577  public totalCount(): number {
578    return 0;
579  }
580
581  public getData(index: number): string {
582    return this.originDataArray[index];
583  }
584
585  registerDataChangeListener(listener: DataChangeListener): void {
586    if (this.listeners.indexOf(listener) < 0) {
587      console.info('add listener');
588      this.listeners.push(listener);
589    }
590  }
591
592  unregisterDataChangeListener(listener: DataChangeListener): void {
593    const pos = this.listeners.indexOf(listener);
594    if (pos >= 0) {
595      console.info('remove listener');
596      this.listeners.splice(pos, 1);
597    }
598  }
599
600  notifyDataReload(): void {
601    this.listeners.forEach(listener => {
602      listener.onDataReloaded();
603    })
604  }
605
606  notifyDataAdd(index: number): void {
607    this.listeners.forEach(listener => {
608      listener.onDataAdd(index);
609    })
610  }
611
612  notifyDataChange(index: number): void {
613    this.listeners.forEach(listener => {
614      listener.onDataChange(index);
615    })
616  }
617
618  notifyDataDelete(index: number): void {
619    this.listeners.forEach(listener => {
620      listener.onDataDelete(index);
621    })
622  }
623}
624
625class MyDataSource extends BasicDataSource {
626  private dataArray: string[] = [];
627
628  public totalCount(): number {
629    return this.dataArray.length;
630  }
631
632  public getData(index: number): string {
633    return this.dataArray[index];
634  }
635
636  public addData(index: number, data: string): void {
637    this.dataArray.splice(index, 0, data);
638    this.notifyDataAdd(index);
639  }
640
641  public pushData(data: string): void {
642    this.dataArray.push(data);
643    this.notifyDataAdd(this.dataArray.length - 1);
644  }
645
646  public deleteData(index: number): void {
647    this.dataArray.splice(index, 1);
648    this.notifyDataDelete(index);
649  }
650
651  public changeData(index: number, data: string): void {
652    this.dataArray.splice(index, 1, data);
653    this.notifyDataChange(index);
654  }
655}
656
657@Entry
658@Component
659struct MyComponent {
660  private moved: number[] = [];
661  private data: MyDataSource = new MyDataSource();
662
663  aboutToAppear() {
664    for (let i = 0; i <= 20; i++) {
665      this.data.pushData(`Hello ${i}`)
666    }
667  }
668
669
670  build() {
671    List({ space: 3 }) {
672      LazyForEach(this.data, (item: string, index: number) => {
673        ListItem() {
674          Row() {
675            Text(item).fontSize(50)
676              .onAppear(() => {
677                console.info("appear:" + item)
678              })
679          }.margin({ left: 10, right: 10 })
680        }
681        .onClick(() => {
682          this.data.changeData(index, item + '00');
683        })
684      }, (item: string) => item)
685    }.cachedCount(5)
686  }
687}
688```
689
690当我们点击`LazyForEach`的子组件时,首先改变当前数据,然后调用数据源`data`的`changeData`方法,在该方法内会调用`notifyDataChange`方法。在`notifyDataChange`方法内会又调用`listener.onDataChange`方法,该方法通知`LazyForEach`组件该处有数据发生变化,`LazyForEach`便会在对应索引处重建子组件。
691
692运行效果如下图所示。
693
694**图5**  LazyForEach改变单个数据
695![LazyForEach-Change-SingleData](./figures/LazyForEach-Change-SingleData.gif)
696
697- ### 改变多个数据
698
699```ts
700class BasicDataSource implements IDataSource {
701  private listeners: DataChangeListener[] = [];
702  private originDataArray: string[] = [];
703
704  public totalCount(): number {
705    return 0;
706  }
707
708  public getData(index: number): string {
709    return this.originDataArray[index];
710  }
711
712  registerDataChangeListener(listener: DataChangeListener): void {
713    if (this.listeners.indexOf(listener) < 0) {
714      console.info('add listener');
715      this.listeners.push(listener);
716    }
717  }
718
719  unregisterDataChangeListener(listener: DataChangeListener): void {
720    const pos = this.listeners.indexOf(listener);
721    if (pos >= 0) {
722      console.info('remove listener');
723      this.listeners.splice(pos, 1);
724    }
725  }
726
727  notifyDataReload(): void {
728    this.listeners.forEach(listener => {
729      listener.onDataReloaded();
730    })
731  }
732
733  notifyDataAdd(index: number): void {
734    this.listeners.forEach(listener => {
735      listener.onDataAdd(index);
736    })
737  }
738
739  notifyDataChange(index: number): void {
740    this.listeners.forEach(listener => {
741      listener.onDataChange(index);
742    })
743  }
744
745  notifyDataDelete(index: number): void {
746    this.listeners.forEach(listener => {
747      listener.onDataDelete(index);
748    })
749  }
750}
751
752class MyDataSource extends BasicDataSource {
753  private dataArray: string[] = [];
754
755  public totalCount(): number {
756    return this.dataArray.length;
757  }
758
759  public getData(index: number): string {
760    return this.dataArray[index];
761  }
762
763  public addData(index: number, data: string): void {
764    this.dataArray.splice(index, 0, data);
765    this.notifyDataAdd(index);
766  }
767
768  public pushData(data: string): void {
769    this.dataArray.push(data);
770    this.notifyDataAdd(this.dataArray.length - 1);
771  }
772
773  public deleteData(index: number): void {
774    this.dataArray.splice(index, 1);
775    this.notifyDataDelete(index);
776  }
777
778  public changeData(index: number): void {
779    this.notifyDataChange(index);
780  }
781
782  public reloadData(): void {
783    this.notifyDataReload();
784  }
785
786  public modifyAllData(): void {
787    this.dataArray = this.dataArray.map((item: string) => {
788        return item + '0';
789    })
790  }
791}
792
793@Entry
794@Component
795struct MyComponent {
796  private moved: number[] = [];
797  private data: MyDataSource = new MyDataSource();
798
799  aboutToAppear() {
800    for (let i = 0; i <= 20; i++) {
801      this.data.pushData(`Hello ${i}`)
802    }
803  }
804
805  build() {
806    List({ space: 3 }) {
807      LazyForEach(this.data, (item: string, index: number) => {
808        ListItem() {
809          Row() {
810            Text(item).fontSize(50)
811              .onAppear(() => {
812                console.info("appear:" + item)
813              })
814          }.margin({ left: 10, right: 10 })
815        }
816        .onClick(() => {
817          this.data.modifyAllData();
818          this.data.reloadData();
819        })
820      }, (item: string) => item)
821    }.cachedCount(5)
822  }
823}
824```
825
826当我们点击`LazyForEach`的子组件时,首先调用`data`的`modifyAllData`方法改变了数据源中的所有数据,然后调用数据源的`reloadData`方法,在该方法内会调用`notifyDataReload`方法。在`notifyDataReload`方法内会又调用`listener.onDataReloaded`方法,通知`LazyForEach`需要重建所有子节点。`LazyForEach`会将原所有数据项和新所有数据项一一做键值比对,若有相同键值则使用缓存,若键值不同则重新构建。
827
828运行效果如下图所示。
829
830**图6**  LazyForEach改变多个数据
831![LazyForEach-Reload-Data](./figures/LazyForEach-Reload-Data.gif)
832
833- ### 改变数据子属性
834
835若仅靠`LazyForEach`的刷新机制,当`item`变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件结构较为复杂的情况下,靠改变键值去刷新渲染性能较低。因此框架提供了`@Observed`与@`ObjectLink`机制进行深度观测,可以做到仅刷新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种刷新方式。
836
837```ts
838class BasicDataSource implements IDataSource {
839  private listeners: DataChangeListener[] = [];
840  private originDataArray: StringData[] = [];
841
842  public totalCount(): number {
843    return 0;
844  }
845
846  public getData(index: number): StringData {
847    return this.originDataArray[index];
848  }
849
850  registerDataChangeListener(listener: DataChangeListener): void {
851    if (this.listeners.indexOf(listener) < 0) {
852      console.info('add listener');
853      this.listeners.push(listener);
854    }
855  }
856
857  unregisterDataChangeListener(listener: DataChangeListener): void {
858    const pos = this.listeners.indexOf(listener);
859    if (pos >= 0) {
860      console.info('remove listener');
861      this.listeners.splice(pos, 1);
862    }
863  }
864
865  notifyDataReload(): void {
866    this.listeners.forEach(listener => {
867      listener.onDataReloaded();
868    })
869  }
870
871  notifyDataAdd(index: number): void {
872    this.listeners.forEach(listener => {
873      listener.onDataAdd(index);
874    })
875  }
876
877  notifyDataChange(index: number): void {
878    this.listeners.forEach(listener => {
879      listener.onDataChange(index);
880    })
881  }
882
883  notifyDataDelete(index: number): void {
884    this.listeners.forEach(listener => {
885      listener.onDataDelete(index);
886    })
887  }
888}
889
890class MyDataSource extends BasicDataSource {
891  private dataArray: StringData[] = [];
892
893  public totalCount(): number {
894    return this.dataArray.length;
895  }
896
897  public getData(index: number): StringData {
898    return this.dataArray[index];
899  }
900
901  public addData(index: number, data: StringData): void {
902    this.dataArray.splice(index, 0, data);
903    this.notifyDataAdd(index);
904  }
905
906  public pushData(data: StringData): void {
907    this.dataArray.push(data);
908    this.notifyDataAdd(this.dataArray.length - 1);
909  }
910}
911
912@Observed
913class StringData {
914  message: string;
915  constructor(message: string) {
916    this.message = message;
917  }
918}
919
920@Entry
921@Component
922struct MyComponent {
923  private moved: number[] = [];
924  @State data: MyDataSource = new MyDataSource();
925
926  aboutToAppear() {
927    for (let i = 0; i <= 20; i++) {
928      this.data.pushData(new StringData(`Hello ${i}`));
929    }
930  }
931
932  build() {
933    List({ space: 3 }) {
934      LazyForEach(this.data, (item: StringData, index: number) => {
935        ListItem() {
936          ChildComponent({data: item})
937        }
938        .onClick(() => {
939          item.message += '0';
940        })
941      }, (item: StringData, index: number) => index.toString())
942    }.cachedCount(5)
943  }
944}
945
946@Component
947struct ChildComponent {
948  @ObjectLink data: StringData
949  build() {
950    Row() {
951      Text(this.data.message).fontSize(50)
952        .onAppear(() => {
953          console.info("appear:" + this.data.message)
954        })
955    }.margin({ left: 10, right: 10 })
956  }
957}
958```
959
960此时点击`LazyForEach`子组件改变`item.message`时,重渲染依赖的是`ChildComponent`的`@ObjectLink`成员变量对其子属性的监听,此时框架只会刷新`Text(this.data.message)`,不会去重建整个`ListItem`子组件。
961
962**图7**  LazyForEach改变数据子属性
963![LazyForEach-Change-SubProperty](./figures/LazyForEach-Change-SubProperty.gif)
964
965## 常见使用问题
966
967- ### 渲染结果非预期
968
969  ```ts
970  class BasicDataSource implements IDataSource {
971    private listeners: DataChangeListener[] = [];
972    private originDataArray: string[] = [];
973
974    public totalCount(): number {
975      return 0;
976    }
977
978    public getData(index: number): string {
979      return this.originDataArray[index];
980    }
981
982    registerDataChangeListener(listener: DataChangeListener): void {
983      if (this.listeners.indexOf(listener) < 0) {
984        console.info('add listener');
985        this.listeners.push(listener);
986      }
987    }
988
989    unregisterDataChangeListener(listener: DataChangeListener): void {
990      const pos = this.listeners.indexOf(listener);
991      if (pos >= 0) {
992        console.info('remove listener');
993        this.listeners.splice(pos, 1);
994      }
995    }
996
997    notifyDataReload(): void {
998      this.listeners.forEach(listener => {
999        listener.onDataReloaded();
1000      })
1001    }
1002
1003    notifyDataAdd(index: number): void {
1004      this.listeners.forEach(listener => {
1005        listener.onDataAdd(index);
1006      })
1007    }
1008
1009    notifyDataChange(index: number): void {
1010      this.listeners.forEach(listener => {
1011        listener.onDataChange(index);
1012      })
1013    }
1014
1015    notifyDataDelete(index: number): void {
1016      this.listeners.forEach(listener => {
1017        listener.onDataDelete(index);
1018      })
1019    }
1020  }
1021
1022  class MyDataSource extends BasicDataSource {
1023    private dataArray: string[] = [];
1024
1025    public totalCount(): number {
1026      return this.dataArray.length;
1027    }
1028
1029    public getData(index: number): string {
1030      return this.dataArray[index];
1031    }
1032
1033    public addData(index: number, data: string): void {
1034      this.dataArray.splice(index, 0, data);
1035      this.notifyDataAdd(index);
1036    }
1037
1038    public pushData(data: string): void {
1039      this.dataArray.push(data);
1040      this.notifyDataAdd(this.dataArray.length - 1);
1041    }
1042
1043    public deleteData(index: number): void {
1044      this.dataArray.splice(index, 1);
1045      this.notifyDataDelete(index);
1046    }
1047  }
1048
1049  @Entry
1050  @Component
1051  struct MyComponent {
1052    private data: MyDataSource = new MyDataSource();
1053
1054    aboutToAppear() {
1055      for (let i = 0; i <= 20; i++) {
1056        this.data.pushData(`Hello ${i}`)
1057      }
1058    }
1059
1060    build() {
1061      List({ space: 3 }) {
1062        LazyForEach(this.data, (item: string, index: number) => {
1063          ListItem() {
1064            Row() {
1065              Text(item).fontSize(50)
1066                .onAppear(() => {
1067                  console.info("appear:" + item)
1068                })
1069            }.margin({ left: 10, right: 10 })
1070          }
1071          .onClick(() => {
1072            // 点击删除子组件
1073            this.data.deleteData(index);
1074          })
1075        }, (item: string) => item)
1076      }.cachedCount(5)
1077    }
1078  }
1079  ```
1080
1081  **图8**  LazyForEach删除数据非预期
1082  ![LazyForEach-Render-Not-Expected](./figures/LazyForEach-Render-Not-Expected.gif)
1083
1084  当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其`index`均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的`index`,其`itemGenerator`中的`index`并没有发生变化,所以删除结果和预期不符。
1085
1086  修复代码如下所示。
1087
1088  ```ts
1089  class BasicDataSource implements IDataSource {
1090    private listeners: DataChangeListener[] = [];
1091    private originDataArray: string[] = [];
1092
1093    public totalCount(): number {
1094      return 0;
1095    }
1096
1097    public getData(index: number): string {
1098      return this.originDataArray[index];
1099    }
1100
1101    registerDataChangeListener(listener: DataChangeListener): void {
1102      if (this.listeners.indexOf(listener) < 0) {
1103        console.info('add listener');
1104        this.listeners.push(listener);
1105      }
1106    }
1107
1108    unregisterDataChangeListener(listener: DataChangeListener): void {
1109      const pos = this.listeners.indexOf(listener);
1110      if (pos >= 0) {
1111        console.info('remove listener');
1112        this.listeners.splice(pos, 1);
1113      }
1114    }
1115
1116    notifyDataReload(): void {
1117      this.listeners.forEach(listener => {
1118        listener.onDataReloaded();
1119      })
1120    }
1121
1122    notifyDataAdd(index: number): void {
1123      this.listeners.forEach(listener => {
1124        listener.onDataAdd(index);
1125      })
1126    }
1127
1128    notifyDataChange(index: number): void {
1129      this.listeners.forEach(listener => {
1130        listener.onDataChange(index);
1131      })
1132    }
1133
1134    notifyDataDelete(index: number): void {
1135      this.listeners.forEach(listener => {
1136        listener.onDataDelete(index);
1137      })
1138    }
1139  }
1140
1141  class MyDataSource extends BasicDataSource {
1142    private dataArray: string[] = [];
1143
1144    public totalCount(): number {
1145      return this.dataArray.length;
1146    }
1147
1148    public getData(index: number): string {
1149      return this.dataArray[index];
1150    }
1151
1152    public addData(index: number, data: string): void {
1153      this.dataArray.splice(index, 0, data);
1154      this.notifyDataAdd(index);
1155    }
1156
1157    public pushData(data: string): void {
1158      this.dataArray.push(data);
1159      this.notifyDataAdd(this.dataArray.length - 1);
1160    }
1161
1162    public deleteData(index: number): void {
1163      this.dataArray.splice(index, 1);
1164      this.notifyDataDelete(index);
1165    }
1166
1167    public reloadData(): void {
1168      this.notifyDataReload();
1169    }
1170  }
1171
1172  @Entry
1173  @Component
1174  struct MyComponent {
1175    private data: MyDataSource = new MyDataSource();
1176
1177    aboutToAppear() {
1178      for (let i = 0; i <= 20; i++) {
1179        this.data.pushData(`Hello ${i}`)
1180      }
1181    }
1182
1183    build() {
1184      List({ space: 3 }) {
1185        LazyForEach(this.data, (item: string, index: number) => {
1186          ListItem() {
1187            Row() {
1188              Text(item).fontSize(50)
1189                .onAppear(() => {
1190                  console.info("appear:" + item)
1191                })
1192            }.margin({ left: 10, right: 10 })
1193          }
1194          .onClick(() => {
1195            // 点击删除子组件
1196            this.data.deleteData(index);
1197            // 重置所有子组件的index索引
1198            this.data.reloadData();
1199          })
1200        }, (item: string, index: number) => item + index.toString())
1201      }.cachedCount(5)
1202    }
1203  }
1204  ```
1205
1206  在删除一个数据项后调用`reloadData`方法,重建后面的数据项,以达到更新`index`索引的目的。
1207
1208  **图9**  修复LazyForEach删除数据非预期
1209  ![LazyForEach-Render-Not-Expected-Repair](./figures/LazyForEach-Render-Not-Expected-Repair.gif)
1210
1211- ### 重渲染时图片闪烁
1212
1213  ```ts
1214  class BasicDataSource implements IDataSource {
1215    private listeners: DataChangeListener[] = [];
1216    private originDataArray: StringData[] = [];
1217
1218    public totalCount(): number {
1219      return 0;
1220    }
1221
1222    public getData(index: number): StringData {
1223      return this.originDataArray[index];
1224    }
1225
1226    registerDataChangeListener(listener: DataChangeListener): void {
1227      if (this.listeners.indexOf(listener) < 0) {
1228        console.info('add listener');
1229        this.listeners.push(listener);
1230      }
1231    }
1232
1233    unregisterDataChangeListener(listener: DataChangeListener): void {
1234      const pos = this.listeners.indexOf(listener);
1235      if (pos >= 0) {
1236        console.info('remove listener');
1237        this.listeners.splice(pos, 1);
1238      }
1239    }
1240
1241    notifyDataReload(): void {
1242      this.listeners.forEach(listener => {
1243        listener.onDataReloaded();
1244      })
1245    }
1246
1247    notifyDataAdd(index: number): void {
1248      this.listeners.forEach(listener => {
1249        listener.onDataAdd(index);
1250      })
1251    }
1252
1253    notifyDataChange(index: number): void {
1254      this.listeners.forEach(listener => {
1255        listener.onDataChange(index);
1256      })
1257    }
1258
1259    notifyDataDelete(index: number): void {
1260      this.listeners.forEach(listener => {
1261        listener.onDataDelete(index);
1262      })
1263    }
1264  }
1265
1266  class MyDataSource extends BasicDataSource {
1267    private dataArray: StringData[] = [];
1268
1269    public totalCount(): number {
1270      return this.dataArray.length;
1271    }
1272
1273    public getData(index: number): StringData {
1274      return this.dataArray[index];
1275    }
1276
1277    public addData(index: number, data: StringData): void {
1278      this.dataArray.splice(index, 0, data);
1279      this.notifyDataAdd(index);
1280    }
1281
1282    public pushData(data: StringData): void {
1283      this.dataArray.push(data);
1284      this.notifyDataAdd(this.dataArray.length - 1);
1285    }
1286
1287    public reloadData(): void {
1288      this.notifyDataReload();
1289    }
1290  }
1291
1292  class StringData {
1293    message: string;
1294    imgSrc: Resource;
1295    constructor(message: string, imgSrc: Resource) {
1296        this.message = message;
1297        this.imgSrc = imgSrc;
1298    }
1299  }
1300
1301  @Entry
1302  @Component
1303  struct MyComponent {
1304    private moved: number[] = [];
1305    private data: MyDataSource = new MyDataSource();
1306
1307    aboutToAppear() {
1308      for (let i = 0; i <= 20; i++) {
1309        this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
1310      }
1311    }
1312
1313    build() {
1314      List({ space: 3 }) {
1315        LazyForEach(this.data, (item: StringData, index: number) => {
1316          ListItem() {
1317            Column() {
1318              Text(item.message).fontSize(50)
1319                .onAppear(() => {
1320                  console.info("appear:" + item.message)
1321                })
1322              Image(item.imgSrc)
1323                .width(500)
1324                .height(200)
1325            }.margin({ left: 10, right: 10 })
1326          }
1327          .onClick(() => {
1328            item.message += '00';
1329            this.data.reloadData();
1330          })
1331        }, (item: StringData, index: number) => JSON.stringify(item))
1332      }.cachedCount(5)
1333    }
1334  }
1335  ```
1336
1337  **图10**  LazyForEach仅改变文字但是图片闪烁问题
1338  ![LazyForEach-Image-Flush](./figures/LazyForEach-Image-Flush.gif)
1339
1340  在我们点击`ListItem`子组件时,我们只改变了数据项的`message`属性,但是`LazyForEach`的刷新机制会导致整个`ListItem`被重建。由于`Image`组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用`@ObjectLink`和`@Observed`去单独刷新使用了`item.message`的`Text`组件。
1341
1342  修复代码如下所示。
1343
1344  ```ts
1345  class BasicDataSource implements IDataSource {
1346    private listeners: DataChangeListener[] = [];
1347    private originDataArray: StringData[] = [];
1348
1349    public totalCount(): number {
1350      return 0;
1351    }
1352
1353    public getData(index: number): StringData {
1354      return this.originDataArray[index];
1355    }
1356
1357    registerDataChangeListener(listener: DataChangeListener): void {
1358      if (this.listeners.indexOf(listener) < 0) {
1359        console.info('add listener');
1360        this.listeners.push(listener);
1361      }
1362    }
1363
1364    unregisterDataChangeListener(listener: DataChangeListener): void {
1365      const pos = this.listeners.indexOf(listener);
1366      if (pos >= 0) {
1367        console.info('remove listener');
1368        this.listeners.splice(pos, 1);
1369      }
1370    }
1371
1372    notifyDataReload(): void {
1373      this.listeners.forEach(listener => {
1374        listener.onDataReloaded();
1375      })
1376    }
1377
1378    notifyDataAdd(index: number): void {
1379      this.listeners.forEach(listener => {
1380        listener.onDataAdd(index);
1381      })
1382    }
1383
1384    notifyDataChange(index: number): void {
1385      this.listeners.forEach(listener => {
1386        listener.onDataChange(index);
1387      })
1388    }
1389
1390    notifyDataDelete(index: number): void {
1391      this.listeners.forEach(listener => {
1392        listener.onDataDelete(index);
1393      })
1394    }
1395  }
1396
1397  class MyDataSource extends BasicDataSource {
1398    private dataArray: StringData[] = [];
1399
1400    public totalCount(): number {
1401      return this.dataArray.length;
1402    }
1403
1404    public getData(index: number): StringData {
1405      return this.dataArray[index];
1406    }
1407
1408    public addData(index: number, data: StringData): void {
1409      this.dataArray.splice(index, 0, data);
1410      this.notifyDataAdd(index);
1411    }
1412
1413    public pushData(data: StringData): void {
1414      this.dataArray.push(data);
1415      this.notifyDataAdd(this.dataArray.length - 1);
1416    }
1417  }
1418
1419  @Observed
1420  class StringData {
1421    message: string;
1422    imgSrc: Resource;
1423    constructor(message: string, imgSrc: Resource) {
1424        this.message = message;
1425        this.imgSrc = imgSrc;
1426    }
1427  }
1428
1429  @Entry
1430  @Component
1431  struct MyComponent {
1432    @State data: MyDataSource = new MyDataSource();
1433
1434    aboutToAppear() {
1435      for (let i = 0; i <= 20; i++) {
1436        this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
1437      }
1438    }
1439
1440    build() {
1441      List({ space: 3 }) {
1442        LazyForEach(this.data, (item: StringData, index: number) => {
1443          ListItem() {
1444            ChildComponent({data: item})
1445          }
1446          .onClick(() => {
1447            item.message += '0';
1448          })
1449        }, (item: StringData, index: number) => index.toString())
1450      }.cachedCount(5)
1451    }
1452  }
1453
1454  @Component
1455  struct ChildComponent {
1456    @ObjectLink data: StringData
1457    build() {
1458      Column() {
1459        Text(this.data.message).fontSize(50)
1460          .onAppear(() => {
1461            console.info("appear:" + this.data.message)
1462          })
1463        Image(this.data.imgSrc)
1464          .width(500)
1465          .height(200)
1466      }.margin({ left: 10, right: 10 })
1467    }
1468  }
1469  ```
1470
1471  **图11**  修复LazyForEach仅改变文字但是图片闪烁问题
1472  ![LazyForEach-Image-Flush-Repair](./figures/LazyForEach-Image-Flush-Repair.gif)
1473
1474- ### @ObjectLink属性变化UI未更新
1475
1476  ```ts
1477  class BasicDataSource implements IDataSource {
1478    private listeners: DataChangeListener[] = [];
1479    private originDataArray: StringData[] = [];
1480
1481    public totalCount(): number {
1482      return 0;
1483    }
1484
1485    public getData(index: number): StringData {
1486      return this.originDataArray[index];
1487    }
1488
1489    registerDataChangeListener(listener: DataChangeListener): void {
1490      if (this.listeners.indexOf(listener) < 0) {
1491        console.info('add listener');
1492        this.listeners.push(listener);
1493      }
1494    }
1495
1496    unregisterDataChangeListener(listener: DataChangeListener): void {
1497      const pos = this.listeners.indexOf(listener);
1498      if (pos >= 0) {
1499        console.info('remove listener');
1500        this.listeners.splice(pos, 1);
1501      }
1502    }
1503
1504    notifyDataReload(): void {
1505      this.listeners.forEach(listener => {
1506        listener.onDataReloaded();
1507      })
1508    }
1509
1510    notifyDataAdd(index: number): void {
1511      this.listeners.forEach(listener => {
1512        listener.onDataAdd(index);
1513      })
1514    }
1515
1516    notifyDataChange(index: number): void {
1517      this.listeners.forEach(listener => {
1518        listener.onDataChange(index);
1519      })
1520    }
1521
1522    notifyDataDelete(index: number): void {
1523      this.listeners.forEach(listener => {
1524        listener.onDataDelete(index);
1525      })
1526    }
1527  }
1528
1529  class MyDataSource extends BasicDataSource {
1530    private dataArray: StringData[] = [];
1531
1532    public totalCount(): number {
1533      return this.dataArray.length;
1534    }
1535
1536    public getData(index: number): StringData {
1537      return this.dataArray[index];
1538    }
1539
1540    public addData(index: number, data: StringData): void {
1541      this.dataArray.splice(index, 0, data);
1542      this.notifyDataAdd(index);
1543    }
1544
1545    public pushData(data: StringData): void {
1546      this.dataArray.push(data);
1547      this.notifyDataAdd(this.dataArray.length - 1);
1548    }
1549  }
1550
1551  @Observed
1552  class StringData {
1553    message: NestedString;
1554    constructor(message: NestedString) {
1555      this.message = message;
1556    }
1557  }
1558
1559  @Observed
1560  class NestedString {
1561    message: string;
1562    constructor(message: string) {
1563      this.message = message;
1564    }
1565  }
1566
1567  @Entry
1568  @Component
1569  struct MyComponent {
1570    private moved: number[] = [];
1571    @State data: MyDataSource = new MyDataSource();
1572
1573    aboutToAppear() {
1574      for (let i = 0; i <= 20; i++) {
1575        this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
1576      }
1577    }
1578
1579    build() {
1580      List({ space: 3 }) {
1581        LazyForEach(this.data, (item: StringData, index: number) => {
1582          ListItem() {
1583            ChildComponent({data: item})
1584          }
1585          .onClick(() => {
1586            item.message.message += '0';
1587          })
1588        }, (item: StringData, index: number) => item.toString() + index.toString())
1589      }.cachedCount(5)
1590    }
1591  }
1592
1593  @Component
1594  struct ChildComponent {
1595    @ObjectLink data: StringData
1596    build() {
1597      Row() {
1598        Text(this.data.message.message).fontSize(50)
1599          .onAppear(() => {
1600            console.info("appear:" + this.data.message.message)
1601          })
1602      }.margin({ left: 10, right: 10 })
1603    }
1604  }
1605  ```
1606
1607  **图12**  ObjectLink属性变化后UI未更新
1608  ![LazyForEach-ObjectLink-NotRenderUI](./figures/LazyForEach-ObjectLink-NotRenderUI.gif)
1609
1610  @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体[请查看@ObjectLink与@Observed的详细使用方法和限制条件](./arkts-observed-and-objectlink.md)。
1611
1612  修复代码如下所示。
1613
1614  ```ts
1615  class BasicDataSource implements IDataSource {
1616    private listeners: DataChangeListener[] = [];
1617    private originDataArray: StringData[] = [];
1618
1619    public totalCount(): number {
1620      return 0;
1621    }
1622
1623    public getData(index: number): StringData {
1624      return this.originDataArray[index];
1625    }
1626
1627    registerDataChangeListener(listener: DataChangeListener): void {
1628      if (this.listeners.indexOf(listener) < 0) {
1629        console.info('add listener');
1630        this.listeners.push(listener);
1631      }
1632    }
1633
1634    unregisterDataChangeListener(listener: DataChangeListener): void {
1635      const pos = this.listeners.indexOf(listener);
1636      if (pos >= 0) {
1637        console.info('remove listener');
1638        this.listeners.splice(pos, 1);
1639      }
1640    }
1641
1642    notifyDataReload(): void {
1643      this.listeners.forEach(listener => {
1644        listener.onDataReloaded();
1645      })
1646    }
1647
1648    notifyDataAdd(index: number): void {
1649      this.listeners.forEach(listener => {
1650        listener.onDataAdd(index);
1651      })
1652    }
1653
1654    notifyDataChange(index: number): void {
1655      this.listeners.forEach(listener => {
1656        listener.onDataChange(index);
1657      })
1658    }
1659
1660    notifyDataDelete(index: number): void {
1661      this.listeners.forEach(listener => {
1662        listener.onDataDelete(index);
1663      })
1664    }
1665  }
1666
1667  class MyDataSource extends BasicDataSource {
1668    private dataArray: StringData[] = [];
1669
1670    public totalCount(): number {
1671      return this.dataArray.length;
1672    }
1673
1674    public getData(index: number): StringData {
1675      return this.dataArray[index];
1676    }
1677
1678    public addData(index: number, data: StringData): void {
1679      this.dataArray.splice(index, 0, data);
1680      this.notifyDataAdd(index);
1681    }
1682
1683    public pushData(data: StringData): void {
1684      this.dataArray.push(data);
1685      this.notifyDataAdd(this.dataArray.length - 1);
1686    }
1687  }
1688
1689  @Observed
1690  class StringData {
1691    message: NestedString;
1692    constructor(message: NestedString) {
1693      this.message = message;
1694    }
1695  }
1696
1697  @Observed
1698  class NestedString {
1699    message: string;
1700    constructor(message: string) {
1701      this.message = message;
1702    }
1703  }
1704
1705  @Entry
1706  @Component
1707  struct MyComponent {
1708    private moved: number[] = [];
1709    @State data: MyDataSource = new MyDataSource();
1710
1711    aboutToAppear() {
1712      for (let i = 0; i <= 20; i++) {
1713        this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
1714      }
1715    }
1716
1717    build() {
1718      List({ space: 3 }) {
1719        LazyForEach(this.data, (item: StringData, index: number) => {
1720          ListItem() {
1721            ChildComponent({data: item})
1722          }
1723          .onClick(() => {
1724            item.message = new NestedString(item.message.message + '0');
1725          })
1726        }, (item: StringData, index: number) => item.toString() + index.toString())
1727      }.cachedCount(5)
1728    }
1729  }
1730
1731  @Component
1732  struct ChildComponent {
1733    @ObjectLink data: StringData
1734    build() {
1735      Row() {
1736        Text(this.data.message.message).fontSize(50)
1737          .onAppear(() => {
1738            console.info("appear:" + this.data.message.message)
1739          })
1740      }.margin({ left: 10, right: 10 })
1741    }
1742  }
1743  ```
1744
1745  **图13**  修复ObjectLink属性变化后UI更新
1746  ![LazyForEach-ObjectLink-NotRenderUI-Repair](./figures/LazyForEach-ObjectLink-NotRenderUI-Repair.gif)
1747