渣渣兔 发表于 2024-10-26 01:54:56

(五)鸿蒙HarmonyOS主力开发语言ArkTS-数据懒加载(LazyForEach)

系列文章目录

(一)鸿蒙HarmonyOS开发底子
(二)鸿蒙HarmonyOS主力开发语言ArkTS-根本语法
(三)鸿蒙HarmonyOS主力开发语言ArkTS-状态管理
(四)鸿蒙HarmonyOS主力开发语言ArkTS-渲染控制


LazyForEach:数据懒加载

LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视地区按需创建组件,当组件滑出可视地区外时,框架会举行组件销毁回收以降低内存占用。
https://i-blog.csdnimg.cn/blog_migrate/a8508be99aa748de99e8c309ae74ef20.png#pic_center
接口描述


1.LazyForEach(
2.      dataSource: IDataSource,             // 需要进行数据迭代的数据源
3.      itemGenerator: (item: any, index: number) => void,// 子组件生成函数
4.      keyGenerator?: (item: any, index: number) => string // 键值生成函数
5.): void


参数:
参数名
参数类型
必填
参数描述
dataSource
IDataSource

LazyForEach数据源,需要开发者实现相干接口。
itemGenerator
(item: any, index:number) => void

子组件天生函数,为数组中的每一个数据项创建一个子组件。
说明:
item是当前数据项,index是数据项索引值。
itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须天生一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不答应使用ForEach和LazyForEach语句。
keyGenerator
(item: any, index:number) => string

键值天生函数,用于给数据源中的每一个数据项天生唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项更换时,被更换项的键值和新项的键值必须不同。键值天生器的功能是可选的,但是,为了使开发框架能够更好地辨认数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值天生器,则LazyForEach中的所有节点都将重建。
说明:
item是当前数据项,index是数据项索引值。
数据源中的每一个数据项天生的键值不能重复。
IDataSource类型说明


1.interface IDataSource {
2.      totalCount(): number; // 获得数据总数
3.      getData(index: number): Object; // 获取索引值对应的数据
4.      registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
5.      unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
6.}


接口声明
参数类型
说明
totalCount(): number
-
获得数据总数。
getData(index: number): any
number
获取索引值index对应的数据。
index:获取数据对应的索引值。
registerDataChangeListener(listener:DataChangeListener): void
DataChangeListener
注册数据改变的监听器。
listener:数据变化监听器
unregisterDataChangeListener(listener:DataChangeListener): void
DataChangeListener
注销数据改变的监听器。
listener:数据变化监听器
DataChangeListener类型说明


1.interface DataChangeListener {
2.      onDataReloaded(): void; // 重新加载数据完成后调用
3.      onDataAdded(index: number): void; // 添加数据完成后调用
4.      onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
5.      onDataDeleted(index: number): void; // 删除数据完成后调用
6.      onDataChanged(index: number): void; // 改变数据完成后调用
7.      onDataAdd(index: number): void; // 添加数据完成后调用
8.      onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
9.      onDataDelete(index: number): void; // 删除数据完成后调用
10.      onDataChange(index: number): void; // 改变数据完成后调用
11.}


接口声明
参数类型
说明
onDataReloaded(): void
-
通知组件重新加载所有数据。
键值没有变化的数据项会使用原先的子组件,键值发生变化的会重建子组件。
onDataAdd(index: number): void8+
number
通知组件index的位置有数据添加。
index:数据添加位置的索引值。
onDataMove(from: number, to: number): void8+
from: number,
to: number
通知组件数据有移动。
from: 数据移动起始位置,to: 数据移动目标位置。
说明:
数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。
onDataDelete(index: number):void8+
number
通知组件删除index位置的数据并革新LazyForEach的展示内容。
index:数据删除位置的索引值。
说明:
需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。
onDataChange(index: number): void8+
number
通知组件index的位置有数据有变化。
index:数据变化位置的索引值。
onDataAdded(index: number):void(deprecated)
number
通知组件index的位置有数据添加。
从API 8开始,建议使用onDataAdd。
index:数据添加位置的索引值。
onDataMoved(from: number, to: number): void(deprecated)
from: number,
to: number
通知组件数据有移动。
从API 8开始,建议使用onDataMove。
from: 数据移动起始位置,to: 数据移动目标位置。
将from和to位置的数据举行互换。
说明:
数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。
onDataDeleted(index: number):void(deprecated)
number
通知组件删除index位置的数据并革新LazyForEach的展示内容。
从API 8开始,建议使用onDataDelete。
index:数据删除位置的索引值。
onDataChanged(index: number): void(deprecated)
number
通知组件index的位置有数据有变化。
从API 8开始,建议使用onDataChange。
index:数据变化监听器。
使用限定



[*]LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
[*]LazyForEach在每次迭代中,必须创建且只答应创建一个子组件。
[*]天生的子组件必须是答应包罗在LazyForEach父容器组件中的子组件。
[*]答应LazyForEach包罗在if/else条件渲染语句中,也答应LazyForEach中出现if/else条件渲染语句。
[*]键值天生器必须针对每个数据天生唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。
[*]LazyForEach必须使用DataChangeListener对象来举行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI革新。
[*]为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要天生不同于原来的键值来触发组件革新。
键值天生规则

在LazyForEach循环渲染过程中,系统会为每个item天生一个唯一且长期的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被更换或修改,并会基于新的键值创建一个新的组件。
LazyForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的天生规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值天生函数,即(item: any, index: number) => { return viewId + ‘-’ + index.toString(); }, viewId在编译器转换过程中天生,同一个LazyForEach组件内其viewId是一致的。
组件创建规则

在确定键值天生规则后,LazyForEach的第二个参数itemGenerator函数会根据键值天生规则为数据源的每个数组项创建组件。组件的创建包括两种情况:LazyForEach首次渲染和LazyForEach非首次渲染。
首次渲染



[*]天生不同键值
在LazyForEach首次渲染时,会根据上述键值天生规则为数据源的每个数组项天生唯一键值,并创建相应的组件。

1.// Basic implementation of IDataSource to handle data listener
2.class BasicDataSource implements IDataSource {
3.    private listeners: DataChangeListener[] = [];
4.    private originDataArray: string[] = [];

6.    public totalCount(): number {
7.      return 0;
8.    }

10.    public getData(index: number): string {
11.      return this.originDataArray;
12.    }

14.    // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
15.    registerDataChangeListener(listener: DataChangeListener): void {
16.      if (this.listeners.indexOf(listener) < 0) {
17.      console.info('add listener');
18.      this.listeners.push(listener);
19.      }
20.    }

22.    // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
23.    unregisterDataChangeListener(listener: DataChangeListener): void {
24.      const pos = this.listeners.indexOf(listener);
25.      if (pos >= 0) {
26.      console.info('remove listener');
27.      this.listeners.splice(pos, 1);
28.      }
29.    }

31.    // 通知LazyForEach组件需要重载所有子组件
32.    notifyDataReload(): void {
33.      this.listeners.forEach(listener => {
34.      listener.onDataReloaded();
35.      })
36.    }

38.    // 通知LazyForEach组件需要在index对应索引处添加子组件
39.    notifyDataAdd(index: number): void {
40.      this.listeners.forEach(listener => {
41.      listener.onDataAdd(index);
42.      })
43.    }

45.    // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
46.    notifyDataChange(index: number): void {
47.      this.listeners.forEach(listener => {
48.      listener.onDataChange(index);
49.      })
50.    }

52.    // 通知LazyForEach组件需要在index对应索引处删除该子组件
53.    notifyDataDelete(index: number): void {
54.      this.listeners.forEach(listener => {
55.      listener.onDataDelete(index);
56.      })
57.    }
58.}

60.class MyDataSource extends BasicDataSource {
61.    private dataArray: string[] = [];

63.    public totalCount(): number {
64.      return this.dataArray.length;
65.    }

67.    public getData(index: number): string {
68.      return this.dataArray;
69.    }

71.    public addData(index: number, data: string): void {
72.      this.dataArray.splice(index, 0, data);
73.      this.notifyDataAdd(index);
74.    }

76.    public pushData(data: string): void {
77.      this.dataArray.push(data);
78.      this.notifyDataAdd(this.dataArray.length - 1);
79.    }
80.}

82.@Entry
83.@Component
84.struct MyComponent {
85.    private data: MyDataSource = new MyDataSource();

87.    aboutToAppear() {
88.      for (let i = 0; i <= 20; i++) {
89.      this.data.pushData(`Hello ${i}`)
90.      }
91.    }

93.    build() {
94.      List({ space: 3 }) {
95.      LazyForEach(this.data, (item: string) => {
96.          ListItem() {
97.            Row() {
98.            Text(item).fontSize(50)
99.                .onAppear(() => {
100.                  console.info("appear:" + item)
101.                })
102.            }.margin({ left: 10, right: 10 })
103.          }
104.      }, (item: string) => item)
105.      }.cachedCount(5)
106.    }
107.}


在上述代码中,键值天生规则是keyGenerator函数的返回值item。在LazyForEach循环渲染时,其为数据源数组项依次天生键值Hello 0、Hello 1 … Hello 20,并创建对应的ListItem子组件渲染到界面上。
运行效果如下图所示。
图1 LazyForEach正常首次渲染
https://i-blog.csdnimg.cn/blog_migrate/7ff0bc742327ff64d65d84ddb7634e9e.gif


[*]键值相同时错误渲染
当不同数据项天生的键值相同时,框架的行为是不可猜测的。例如,在以下代码中,LazyForEach渲染的数据项键值均相同,在滑动过程中,LazyForEach会对划入划出当前页面的子组件举行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架大概存在取用缓存错误的情况,导致子组件渲染有问题。

1.class BasicDataSource implements IDataSource {
2.private listeners: DataChangeListener[] = [];
3.private originDataArray: string[] = [];

5.public totalCount(): number {
6.return 0;
7.}

9.public getData(index: number): string {
10.return this.originDataArray;
11.}

13.registerDataChangeListener(listener: DataChangeListener): void {
14.if (this.listeners.indexOf(listener) < 0) {
15.console.info('add listener');
16.this.listeners.push(listener);
17.}
18.}

20.unregisterDataChangeListener(listener: DataChangeListener): void {
21.const pos = this.listeners.indexOf(listener);
22.if (pos >= 0) {
23.console.info('remove listener');
24.this.listeners.splice(pos, 1);
25.}
26.}

28.notifyDataReload(): void {
29.this.listeners.forEach(listener => {
30.listener.onDataReloaded();
31.})
32.}

34.notifyDataAdd(index: number): void {
35.this.listeners.forEach(listener => {
36.listener.onDataAdd(index);
37.})
38.}

40.notifyDataChange(index: number): void {
41.this.listeners.forEach(listener => {
42.listener.onDataChange(index);
43.})
44.}

46.notifyDataDelete(index: number): void {
47.this.listeners.forEach(listener => {
48.listener.onDataDelete(index);
49.})
50.}
51.}

53.class MyDataSource extends BasicDataSource {
54.private dataArray: string[] = [];

56.public totalCount(): number {
57.return this.dataArray.length;
58.}

60.public getData(index: number): string {
61.return this.dataArray;
62.}

64.public addData(index: number, data: string): void {
65.this.dataArray.splice(index, 0, data);
66.this.notifyDataAdd(index);
67.}

69.public pushData(data: string): void {
70.this.dataArray.push(data);
71.this.notifyDataAdd(this.dataArray.length - 1);
72.}
73.}

75.@Entry
76.@Component
77.struct MyComponent {
78.private data: MyDataSource = new MyDataSource();

80.aboutToAppear() {
81.for (let i = 0; i <= 20; i++) {
82.this.data.pushData(`Hello ${i}`)
83.}
84.}

86.build() {
87.List({ space: 3 }) {
88.LazyForEach(this.data, (item: string) => {
89.ListItem() {
90.Row() {
91.Text(item).fontSize(50)
92..onAppear(() => {
93.console.info("appear:" + item)
94.})
95.}.margin({ left: 10, right: 10 })
96.}
97.}, (item: string) => 'same key')
98.}.cachedCount(5)
99.}
100.}
运行效果如下图所示。可以看到Hello 0在滑动过程中被错误渲染为Hello 13。
图2 LazyForEach存在相同键值
https://i-blog.csdnimg.cn/blog_migrate/ec514c015addbd7e10873c0a56a6bbba.gif


[*]非首次渲染
当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知LazyForEach做相应的更新,各使用场景如下。


[*]添加数据

1.class BasicDataSource implements IDataSource {
2.    private listeners: DataChangeListener[] = [];
3.    private originDataArray: string[] = [];

5.    public totalCount(): number {
6.      return 0;
7.    }

9.    public getData(index: number): string {
10.      return this.originDataArray;
11.    }

13.    registerDataChangeListener(listener: DataChangeListener): void {
14.      if (this.listeners.indexOf(listener) < 0) {
15.      console.info('add listener');
16.      this.listeners.push(listener);
17.      }
18.    }

20.    unregisterDataChangeListener(listener: DataChangeListener): void {
21.      const pos = this.listeners.indexOf(listener);
22.      if (pos >= 0) {
23.      console.info('remove listener');
24.      this.listeners.splice(pos, 1);
25.      }
26.    }

28.    notifyDataReload(): void {
29.      this.listeners.forEach(listener => {
30.      listener.onDataReloaded();
31.      })
32.    }

34.    notifyDataAdd(index: number): void {
35.      this.listeners.forEach(listener => {
36.      listener.onDataAdd(index);
37.      })
38.    }

40.    notifyDataChange(index: number): void {
41.      this.listeners.forEach(listener => {
42.      listener.onDataChange(index);
43.      })
44.    }

46.    notifyDataDelete(index: number): void {
47.      this.listeners.forEach(listener => {
48.      listener.onDataDelete(index);
49.      })
50.    }
51.}

53.class MyDataSource extends BasicDataSource {
54.    private dataArray: string[] = [];

56.    public totalCount(): number {
57.      return this.dataArray.length;
58.    }

60.    public getData(index: number): string {
61.      return this.dataArray;
62.    }

64.    public addData(index: number, data: string): void {
65.      this.dataArray.splice(index, 0, data);
66.      this.notifyDataAdd(index);
67.    }

69.    public pushData(data: string): void {
70.      this.dataArray.push(data);
71.      this.notifyDataAdd(this.dataArray.length - 1);
72.    }
73.}

75.@Entry
76.@Component
77.struct MyComponent {
78.    private data: MyDataSource = new MyDataSource();

80.    aboutToAppear() {
81.      for (let i = 0; i <= 20; i++) {
82.      this.data.pushData(`Hello ${i}`)
83.      }
84.    }

86.    build() {
87.      List({ space: 3 }) {
88.      LazyForEach(this.data, (item: string) => {
89.          ListItem() {
90.            Row() {
91.            Text(item).fontSize(50)
92.                .onAppear(() => {
93.                  console.info("appear:" + item)
94.                })
95.            }.margin({ left: 10, right: 10 })
96.          }
97.          .onClick(() => {
98.            // 点击追加子组件
99.            this.data.pushData(`Hello ${this.data.totalCount()}`);
100.          })
101.      }, (item: string) => item)
102.      }.cachedCount(5)
103.    }
104.}


当我们点击LazyForEach的子组件时,首先调用数据源data的pushData方法,该方法会在数据源末尾添加数据并调用notifyDataAdd方法。在notifyDataAdd方法内会又调用listener.onDataAdd方法,该方法会通知LazyForEach在该处有数据添加,LazyForEach便会在该索引处新建子组件。
运行效果如下图所示。
图3 LazyForEach添加数据
https://i-blog.csdnimg.cn/blog_migrate/9bddee480b0180d5f51f52ab88a71da1.gif

1.class BasicDataSource implements IDataSource {
2.private listeners: DataChangeListener[] = [];
3.private originDataArray: string[] = [];

5.public totalCount(): number {
6.return 0;
7.}

9.public getData(index: number): string {
10.return this.originDataArray;
11.}

13.registerDataChangeListener(listener: DataChangeListener): void {
14.if (this.listeners.indexOf(listener) < 0) {
15.console.info('add listener');
16.this.listeners.push(listener);
17.}
18.}

20.unregisterDataChangeListener(listener: DataChangeListener): void {
21.const pos = this.listeners.indexOf(listener);
22.if (pos >= 0) {
23.console.info('remove listener');
24.this.listeners.splice(pos, 1);
25.}
26.}

28.notifyDataReload(): void {
29.this.listeners.forEach(listener => {
30.listener.onDataReloaded();
31.})
32.}

34.notifyDataAdd(index: number): void {
35.this.listeners.forEach(listener => {
36.listener.onDataAdd(index);
37.})
38.}

40.notifyDataChange(index: number): void {
41.this.listeners.forEach(listener => {
42.listener.onDataChange(index);
43.})
44.}

46.notifyDataDelete(index: number): void {
47.this.listeners.forEach(listener => {
48.listener.onDataDelete(index);
49.})
50.}
51.}

53.class MyDataSource extends BasicDataSource {
54.dataArray: string[] = [];

56.public totalCount(): number {
57.return this.dataArray.length;
58.}

60.public getData(index: number): string {
61.return this.dataArray;
62.}

64.public addData(index: number, data: string): void {
65.this.dataArray.splice(index, 0, data);
66.this.notifyDataAdd(index);
67.}

69.public pushData(data: string): void {
70.this.dataArray.push(data);
71.this.notifyDataAdd(this.dataArray.length - 1);
72.}

74.public deleteData(index: number): void {
75.this.dataArray.splice(index, 1);
76.this.notifyDataDelete(index);
77.}
78.}

80.@Entry
81.@Component
82.struct MyComponent {
83.private data: MyDataSource = new MyDataSource();

85.aboutToAppear() {
86.for (let i = 0; i <= 20; i++) {
87.this.data.pushData(`Hello ${i}`)
88.}
89.}

91.build() {
92.List({ space: 3 }) {
93.LazyForEach(this.data, (item: string, index: number) => {
94.ListItem() {
95.Row() {
96.Text(item).fontSize(50)
97..onAppear(() => {
98.console.info("appear:" + item)
99.})
100.}.margin({ left: 10, right: 10 })
101.}
102..onClick(() => {
103.// 点击删除子组件
104.this.data.deleteData(this.data.dataArray.indexOf(item));
105.})
106.}, (item: string) => item)
107.}.cachedCount(5)
108.}
109.}
当我们点击LazyForEach的子组件时,首先调用数据源data的deleteData方法,该方法会删除数据源对应索引处的数据并调用notifyDataDelete方法。在notifyDataDelete方法内会又调用listener.onDataDelete方法,该方法会通知LazyForEach在该处有数据删除,LazyForEach便会在该索引处删除对应子组件。
运行效果如下图所示。
图4 LazyForEach删除数据
https://i-blog.csdnimg.cn/blog_migrate/c23c6f25adb5a7fe3ef4a5dfe16361b5.gif

1.class BasicDataSource implements IDataSource {
2.private listeners: DataChangeListener[] = [];
3.private originDataArray: string[] = [];

5.public totalCount(): number {
6.return 0;
7.}

9.public getData(index: number): string {
10.return this.originDataArray;
11.}

13.registerDataChangeListener(listener: DataChangeListener): void {
14.if (this.listeners.indexOf(listener) < 0) {
15.console.info('add listener');
16.this.listeners.push(listener);
17.}
18.}

20.unregisterDataChangeListener(listener: DataChangeListener): void {
21.const pos = this.listeners.indexOf(listener);
22.if (pos >= 0) {
23.console.info('remove listener');
24.this.listeners.splice(pos, 1);
25.}
26.}

28.notifyDataReload(): void {
29.this.listeners.forEach(listener => {
30.listener.onDataReloaded();
31.})
32.}

34.notifyDataAdd(index: number): void {
35.this.listeners.forEach(listener => {
36.listener.onDataAdd(index);
37.})
38.}

40.notifyDataChange(index: number): void {
41.this.listeners.forEach(listener => {
42.listener.onDataChange(index);
43.})
44.}

46.notifyDataDelete(index: number): void {
47.this.listeners.forEach(listener => {
48.listener.onDataDelete(index);
49.})
50.}
51.}

53.class MyDataSource extends BasicDataSource {
54.private dataArray: string[] = [];

56.public totalCount(): number {
57.return this.dataArray.length;
58.}

60.public getData(index: number): string {
61.return this.dataArray;
62.}

64.public addData(index: number, data: string): void {
65.this.dataArray.splice(index, 0, data);
66.this.notifyDataAdd(index);
67.}

69.public pushData(data: string): void {
70.this.dataArray.push(data);
71.this.notifyDataAdd(this.dataArray.length - 1);
72.}

74.public deleteData(index: number): void {
75.this.dataArray.splice(index, 1);
76.this.notifyDataDelete(index);
77.}

79.public changeData(index: number, data: string): void {
80.this.dataArray.splice(index, 1, data);
81.this.notifyDataChange(index);
82.}
83.}

85.@Entry
86.@Component
87.struct MyComponent {
88.private moved: number[] = [];
89.private data: MyDataSource = new MyDataSource();

91.aboutToAppear() {
92.for (let i = 0; i <= 20; i++) {
93.this.data.pushData(`Hello ${i}`)
94.}
95.}

98.build() {
99.List({ space: 3 }) {
100.LazyForEach(this.data, (item: string, index: number) => {
101.ListItem() {
102.Row() {
103.Text(item).fontSize(50)
104..onAppear(() => {
105.console.info("appear:" + item)
106.})
107.}.margin({ left: 10, right: 10 })
108.}
109..onClick(() => {
110.this.data.changeData(index, item + '00');
111.})
112.}, (item: string) => item)
113.}.cachedCount(5)
114.}
115.}
当我们点击LazyForEach的子组件时,首先改变当前数据,然后调用数据源data的changeData方法,在该方法内会调用notifyDataChange方法。在notifyDataChange方法内会又调用listener.onDataChange方法,该方法通知LazyForEach组件该处有数据发生变化,LazyForEach便会在对应索引处重建子组件。
运行效果如下图所示。
图5 LazyForEach改变单个数据
https://i-blog.csdnimg.cn/blog_migrate/e89aa491f689f03e387ac58989290ca3.gif

1.class BasicDataSource implements IDataSource {
2.private listeners: DataChangeListener[] = [];
3.private originDataArray: string[] = [];

5.public totalCount(): number {
6.return 0;
7.}

9.public getData(index: number): string {
10.return this.originDataArray;
11.}

13.registerDataChangeListener(listener: DataChangeListener): void {
14.if (this.listeners.indexOf(listener) < 0) {
15.console.info('add listener');
16.this.listeners.push(listener);
17.}
18.}

20.unregisterDataChangeListener(listener: DataChangeListener): void {
21.const pos = this.listeners.indexOf(listener);
22.if (pos >= 0) {
23.console.info('remove listener');
24.this.listeners.splice(pos, 1);
25.}
26.}

28.notifyDataReload(): void {
29.this.listeners.forEach(listener => {
30.listener.onDataReloaded();
31.})
32.}

34.notifyDataAdd(index: number): void {
35.this.listeners.forEach(listener => {
36.listener.onDataAdd(index);
37.})
38.}

40.notifyDataChange(index: number): void {
41.this.listeners.forEach(listener => {
42.listener.onDataChange(index);
43.})
44.}

46.notifyDataDelete(index: number): void {
47.this.listeners.forEach(listener => {
48.listener.onDataDelete(index);
49.})
50.}
51.}

53.class MyDataSource extends BasicDataSource {
54.private dataArray: string[] = [];

56.public totalCount(): number {
57.return this.dataArray.length;
58.}

60.public getData(index: number): string {
61.return this.dataArray;
62.}

64.public addData(index: number, data: string): void {
65.this.dataArray.splice(index, 0, data);
66.this.notifyDataAdd(index);
67.}

69.public pushData(data: string): void {
70.this.dataArray.push(data);
71.this.notifyDataAdd(this.dataArray.length - 1);
72.}

74.public deleteData(index: number): void {
75.this.dataArray.splice(index, 1);
76.this.notifyDataDelete(index);
77.}

79.public changeData(index: number): void {
80.this.notifyDataChange(index);
81.}

83.public reloadData(): void {
84.this.notifyDataReload();
85.}

87.public modifyAllData(): void {
88.this.dataArray = this.dataArray.map((item: string) => {
89.return item + '0';
90.})
91.}
92.}

94.@Entry
95.@Component
96.struct MyComponent {
97.private moved: number[] = [];
98.private data: MyDataSource = new MyDataSource();

100.aboutToAppear() {
101.for (let i = 0; i <= 20; i++) {
102.this.data.pushData(`Hello ${i}`)
103.}
104.}

106.build() {
107.List({ space: 3 }) {
108.LazyForEach(this.data, (item: string, index: number) => {
109.ListItem() {
110.Row() {
111.Text(item).fontSize(50)
112..onAppear(() => {
113.console.info("appear:" + item)
114.})
115.}.margin({ left: 10, right: 10 })
116.}
117..onClick(() => {
118.this.data.modifyAllData();
119.this.data.reloadData();
120.})
121.}, (item: string) => item)
122.}.cachedCount(5)
123.}
124.}
当我们点击LazyForEach的子组件时,首先调用data的modifyAllData方法改变了数据源中的所有数据,然后调用数据源的reloadData方法,在该方法内会调用notifyDataReload方法。在notifyDataReload方法内会又调用listener.onDataReloaded方法,通知LazyForEach需要重建所有子节点。LazyForEach会将原所有数据项和新所有数据项一一做键值比对,如有相同键值则使用缓存,若键值不同则重新构建。
运行效果如下图所示。
图6 LazyForEach改变多个数据
https://i-blog.csdnimg.cn/blog_migrate/e75c350813ccd80d4f652892c1d68f63.gif
若仅靠LazyForEach的革新机制,当item变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件布局较为复杂的情况下,靠改变键值去革新渲染性能较低。因此框架提供了@Observed与@ObjectLink机制举行深度观测,可以做到仅革新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种革新方式

1.class BasicDataSource implements IDataSource {
2.private listeners: DataChangeListener[] = [];
3.private originDataArray: StringData[] = [];

5.public totalCount(): number {
6.return 0;
7.}

9.public getData(index: number): StringData {
10.return this.originDataArray;
11.}

13.registerDataChangeListener(listener: DataChangeListener): void {
14.if (this.listeners.indexOf(listener) < 0) {
15.console.info('add listener');
16.this.listeners.push(listener);
17.}
18.}

20.unregisterDataChangeListener(listener: DataChangeListener): void {
21.const pos = this.listeners.indexOf(listener);
22.if (pos >= 0) {
23.console.info('remove listener');
24.this.listeners.splice(pos, 1);
25.}
26.}

28.notifyDataReload(): void {
29.this.listeners.forEach(listener => {
30.listener.onDataReloaded();
31.})
32.}

34.notifyDataAdd(index: number): void {
35.this.listeners.forEach(listener => {
36.listener.onDataAdd(index);
37.})
38.}

40.notifyDataChange(index: number): void {
41.this.listeners.forEach(listener => {
42.listener.onDataChange(index);
43.})
44.}

46.notifyDataDelete(index: number): void {
47.this.listeners.forEach(listener => {
48.listener.onDataDelete(index);
49.})
50.}
51.}

53.class MyDataSource extends BasicDataSource {
54.private dataArray: StringData[] = [];

56.public totalCount(): number {
57.return this.dataArray.length;
58.}

60.public getData(index: number): StringData {
61.return this.dataArray;
62.}

64.public addData(index: number, data: StringData): void {
65.this.dataArray.splice(index, 0, data);
66.this.notifyDataAdd(index);
67.}

69.public pushData(data: StringData): void {
70.this.dataArray.push(data);
71.this.notifyDataAdd(this.dataArray.length - 1);
72.}
73.}

75.@Observed
76.class StringData {
77.message: string;
78.constructor(message: string) {
79.this.message = message;
80.}
81.}

83.@Entry
84.@Component
85.struct MyComponent {
86.private moved: number[] = [];
87.@State data: MyDataSource = new MyDataSource();

89.aboutToAppear() {
90.for (let i = 0; i <= 20; i++) {
91.this.data.pushData(new StringData(`Hello ${i}`));
92.}
93.}

95.build() {
96.List({ space: 3 }) {
97.LazyForEach(this.data, (item: StringData, index: number) => {
98.ListItem() {
99.ChildComponent({data: item})
100.}
101..onClick(() => {
102.item.message += '0';
103.})
104.}, (item: StringData, index: number) => index.toString())
105.}.cachedCount(5)
106.}
107.}

109.@Component
110.struct ChildComponent {
111.@ObjectLink data: StringData
112.build() {
113.Row() {
114.Text(this.data.message).fontSize(50)
115..onAppear(() => {
116.console.info("appear:" + this.data.message)
117.})
118.}.margin({ left: 10, right: 10 })
119.}
120.}
此时点击LazyForEach子组件改变item.message时,重渲染依赖的是ChildComponent的@ObjectLink成员变量对其子属性的监听,此时框架只会革新Text(this.data.message),不会去重建整个ListItem子组件。
图7 LazyForEach改变数据子属性
https://i-blog.csdnimg.cn/blog_migrate/84b2d5e0aaf1df7c08de8553e9129712.gif
常见使用问题


[*] 渲染效果非预期

1.class BasicDataSource implements IDataSource {
2.    private listeners: DataChangeListener[] = [];
3.    private originDataArray: string[] = [];

5.    public totalCount(): number {
6.      return 0;
7.    }

9.    public getData(index: number): string {
10.      return this.originDataArray;
11.    }

13.    registerDataChangeListener(listener: DataChangeListener): void {
14.      if (this.listeners.indexOf(listener) < 0) {
15.      console.info('add listener');
16.      this.listeners.push(listener);
17.      }
18.    }

20.    unregisterDataChangeListener(listener: DataChangeListener): void {
21.      const pos = this.listeners.indexOf(listener);
22.      if (pos >= 0) {
23.      console.info('remove listener');
24.      this.listeners.splice(pos, 1);
25.      }
26.    }

28.    notifyDataReload(): void {
29.      this.listeners.forEach(listener => {
30.      listener.onDataReloaded();
31.      })
32.    }

34.    notifyDataAdd(index: number): void {
35.      this.listeners.forEach(listener => {
36.      listener.onDataAdd(index);
37.      })
38.    }

40.    notifyDataChange(index: number): void {
41.      this.listeners.forEach(listener => {
42.      listener.onDataChange(index);
43.      })
44.    }

46.    notifyDataDelete(index: number): void {
47.      this.listeners.forEach(listener => {
48.      listener.onDataDelete(index);
49.      })
50.    }
51.}

53.class MyDataSource extends BasicDataSource {
54.    private dataArray: string[] = [];

56.    public totalCount(): number {
57.      return this.dataArray.length;
58.    }

60.    public getData(index: number): string {
61.      return this.dataArray;
62.    }

64.    public addData(index: number, data: string): void {
65.      this.dataArray.splice(index, 0, data);
66.      this.notifyDataAdd(index);
67.    }

69.    public pushData(data: string): void {
70.      this.dataArray.push(data);
71.      this.notifyDataAdd(this.dataArray.length - 1);
72.    }

74.    public deleteData(index: number): void {
75.      this.dataArray.splice(index, 1);
76.      this.notifyDataDelete(index);
77.    }
78.}

80.@Entry
81.@Component
82.struct MyComponent {
83.    private data: MyDataSource = new MyDataSource();

85.    aboutToAppear() {
86.      for (let i = 0; i <= 20; i++) {
87.      this.data.pushData(`Hello ${i}`)
88.      }
89.    }

91.    build() {
92.      List({ space: 3 }) {
93.      LazyForEach(this.data, (item: string, index: number) => {
94.          ListItem() {
95.            Row() {
96.            Text(item).fontSize(50)
97.                .onAppear(() => {
98.                  console.info("appear:" + item)
99.                })
100.            }.margin({ left: 10, right: 10 })
101.          }
102.          .onClick(() => {
103.            // 点击删除子组件
104.            this.data.deleteData(index);
105.          })
106.      }, (item: string) => item)
107.      }.cachedCount(5)
108.    }
109.}


图8 LazyForEach删除数据非预期
https://i-blog.csdnimg.cn/blog_migrate/38ee3a5eec187108b0b956bcfc0084ea.gif
当我们多次点击子组件时,会发现删除的并不肯定是我们点击的那个子组件。缘故原由是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其index均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的index,其itemGenerator中的index并没有发生变化,所以删除效果和预期不符。
修复代码如下所示。
   
    1.class BasicDataSource implements IDataSource {
    2.private listeners: DataChangeListener[] = [];
    3.private originDataArray: string[] = [];
   
    5.public totalCount(): number {
    6.return 0;
    7.}
   
    9.public getData(index: number): string {
    10.return this.originDataArray;
    11.}
   
    13.registerDataChangeListener(listener: DataChangeListener): void {
    14.if (this.listeners.indexOf(listener) < 0) {
    15.console.info('add listener');
    16.this.listeners.push(listener);
    17.}
    18.}
   
    20.unregisterDataChangeListener(listener: DataChangeListener): void {
    21.const pos = this.listeners.indexOf(listener);
    22.if (pos >= 0) {
    23.console.info('remove listener');
    24.this.listeners.splice(pos, 1);
    25.}
    26.}
   
    28.notifyDataReload(): void {
    29.this.listeners.forEach(listener => {
    30.listener.onDataReloaded();
    31.})
    32.}
   
    34.notifyDataAdd(index: number): void {
    35.this.listeners.forEach(listener => {
    36.listener.onDataAdd(index);
    37.})
    38.}
   
    40.notifyDataChange(index: number): void {
    41.this.listeners.forEach(listener => {
    42.listener.onDataChange(index);
    43.})
    44.}
   
    46.notifyDataDelete(index: number): void {
    47.this.listeners.forEach(listener => {
    48.listener.onDataDelete(index);
    49.})
    50.}
    51.}
   
    53.class MyDataSource extends BasicDataSource {
    54.private dataArray: string[] = [];
   
    56.public totalCount(): number {
    57.return this.dataArray.length;
    58.}
   
    60.public getData(index: number): string {
    61.return this.dataArray;
    62.}
   
    64.public addData(index: number, data: string): void {
    65.this.dataArray.splice(index, 0, data);
    66.this.notifyDataAdd(index);
    67.}
   
    69.public pushData(data: string): void {
    70.this.dataArray.push(data);
    71.this.notifyDataAdd(this.dataArray.length - 1);
    72.}
   
    74.public deleteData(index: number): void {
    75.this.dataArray.splice(index, 1);
    76.this.notifyDataDelete(index);
    77.}
   
    79.public reloadData(): void {
    80.this.notifyDataReload();
    81.}
    82.}
   
    84.@Entry
    85.@Component
    86.struct MyComponent {
    87.private data: MyDataSource = new MyDataSource();
   
    89.aboutToAppear() {
    90.for (let i = 0; i <= 20; i++) {
    91.this.data.pushData(`Hello ${i}`)
    92.}
    93.}
   
    95.build() {
    96.List({ space: 3 }) {
    97.LazyForEach(this.data, (item: string, index: number) => {
    98.ListItem() {
    99.Row() {
    100.Text(item).fontSize(50)
    101..onAppear(() => {
    102.console.info("appear:" + item)
    103.})
    104.}.margin({ left: 10, right: 10 })
    105.}
    106..onClick(() => {
    107.// 点击删除子组件
    108.this.data.deleteData(index);
    109.// 重置所有子组件的index索引
    110.this.data.reloadData();
    111.})
    112.}, (item: string, index: number) => item + index.toString())
    113.}.cachedCount(5)
    114.}
    115.}
在删除一个数据项后调用reloadData方法,重建反面的数据项,以达到更新index索引的目的。
图9 修复LazyForEach删除数据非预期
https://i-blog.csdnimg.cn/blog_migrate/a38511c82f679fdc0a2e5b4faccf8fe4.gif
重渲染时图片闪烁

1.class BasicDataSource implements IDataSource {
2.private listeners: DataChangeListener[] = [];
3.private originDataArray: StringData[] = [];

5.public totalCount(): number {
6.return 0;
7.}

9.public getData(index: number): StringData {
10.return this.originDataArray;
11.}

13.registerDataChangeListener(listener: DataChangeListener): void {
14.if (this.listeners.indexOf(listener) < 0) {
15.console.info('add listener');
16.this.listeners.push(listener);
17.}
18.}

20.unregisterDataChangeListener(listener: DataChangeListener): void {
21.const pos = this.listeners.indexOf(listener);
22.if (pos >= 0) {
23.console.info('remove listener');
24.this.listeners.splice(pos, 1);
25.}
26.}

28.notifyDataReload(): void {
29.this.listeners.forEach(listener => {
30.listener.onDataReloaded();
31.})
32.}

34.notifyDataAdd(index: number): void {
35.this.listeners.forEach(listener => {
36.listener.onDataAdd(index);
37.})
38.}

40.notifyDataChange(index: number): void {
41.this.listeners.forEach(listener => {
42.listener.onDataChange(index);
43.})
44.}

46.notifyDataDelete(index: number): void {
47.this.listeners.forEach(listener => {
48.listener.onDataDelete(index);
49.})
50.}
51.}

53.class MyDataSource extends BasicDataSource {
54.private dataArray: StringData[] = [];

56.public totalCount(): number {
57.return this.dataArray.length;
58.}

60.public getData(index: number): StringData {
61.return this.dataArray;
62.}

64.public addData(index: number, data: StringData): void {
65.this.dataArray.splice(index, 0, data);
66.this.notifyDataAdd(index);
67.}

69.public pushData(data: StringData): void {
70.this.dataArray.push(data);
71.this.notifyDataAdd(this.dataArray.length - 1);
72.}

74.public reloadData(): void {
75.this.notifyDataReload();
76.}
77.}

79.class StringData {
80.message: string;
81.imgSrc: Resource;
82.constructor(message: string, imgSrc: Resource) {
83.this.message = message;
84.this.imgSrc = imgSrc;
85.}
86.}

88.@Entry
89.@Component
90.struct MyComponent {
91.private moved: number[] = [];
92.private data: MyDataSource = new MyDataSource();

94.aboutToAppear() {
95.for (let i = 0; i <= 20; i++) {
96.this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
97.}
98.}

100.build() {
101.List({ space: 3 }) {
102.LazyForEach(this.data, (item: StringData, index: number) => {
103.ListItem() {
104.Column() {
105.Text(item.message).fontSize(50)
106..onAppear(() => {
107.console.info("appear:" + item.message)
108.})
109.Image(item.imgSrc)
110..width(500)
111..height(200)
112.}.margin({ left: 10, right: 10 })
113.}
114..onClick(() => {
115.item.message += '00';
116.this.data.reloadData();
117.})
118.}, (item: StringData, index: number) => JSON.stringify(item))
119.}.cachedCount(5)
120.}
121.}
图10 LazyForEach仅改变笔墨但是图片闪烁问题
https://i-blog.csdnimg.cn/blog_migrate/4e76e010317ba3096244ae8da88d26b4.gif在我们点击ListItem子组件时,我们只改变了数据项的message属性,但是LazyForEach的革新机制会导致整个ListItem被重建。由于Image组件是异步革新,所以视觉上图片会发生闪烁。为了办理这种情况我们应该使用@ObjectLink和@Observed去单独革新使用了item.message的Text组件。
修复代码如下所示。
   
    1.class BasicDataSource implements IDataSource {
    2.private listeners: DataChangeListener[] = [];
    3.private originDataArray: StringData[] = [];
   
    5.public totalCount(): number {
    6.return 0;
    7.}
   
    9.public getData(index: number): StringData {
    10.return this.originDataArray;
    11.}
   
    13.registerDataChangeListener(listener: DataChangeListener): void {
    14.if (this.listeners.indexOf(listener) < 0) {
    15.console.info('add listener');
    16.this.listeners.push(listener);
    17.}
    18.}
   
    20.unregisterDataChangeListener(listener: DataChangeListener): void {
    21.const pos = this.listeners.indexOf(listener);
    22.if (pos >= 0) {
    23.console.info('remove listener');
    24.this.listeners.splice(pos, 1);
    25.}
    26.}
   
    28.notifyDataReload(): void {
    29.this.listeners.forEach(listener => {
    30.listener.onDataReloaded();
    31.})
    32.}
   
    34.notifyDataAdd(index: number): void {
    35.this.listeners.forEach(listener => {
    36.listener.onDataAdd(index);
    37.})
    38.}
   
    40.notifyDataChange(index: number): void {
    41.this.listeners.forEach(listener => {
    42.listener.onDataChange(index);
    43.})
    44.}
   
    46.notifyDataDelete(index: number): void {
    47.this.listeners.forEach(listener => {
    48.listener.onDataDelete(index);
    49.})
    50.}
    51.}
   
    53.class MyDataSource extends BasicDataSource {
    54.private dataArray: StringData[] = [];
   
    56.public totalCount(): number {
    57.return this.dataArray.length;
    58.}
   
    60.public getData(index: number): StringData {
    61.return this.dataArray;
    62.}
   
    64.public addData(index: number, data: StringData): void {
    65.this.dataArray.splice(index, 0, data);
    66.this.notifyDataAdd(index);
    67.}
   
    69.public pushData(data: StringData): void {
    70.this.dataArray.push(data);
    71.this.notifyDataAdd(this.dataArray.length - 1);
    72.}
    73.}
   
    75.@Observed
    76.class StringData {
    77.message: string;
    78.imgSrc: Resource;
    79.constructor(message: string, imgSrc: Resource) {
    80.this.message = message;
    81.this.imgSrc = imgSrc;
    82.}
    83.}
   
    85.@Entry
    86.@Component
    87.struct MyComponent {
    88.@State data: MyDataSource = new MyDataSource();
   
    90.aboutToAppear() {
    91.for (let i = 0; i <= 20; i++) {
    92.this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
    93.}
    94.}
   
    96.build() {
    97.List({ space: 3 }) {
    98.LazyForEach(this.data, (item: StringData, index: number) => {
    99.ListItem() {
    100.ChildComponent({data: item})
    101.}
    102..onClick(() => {
    103.item.message += '0';
    104.})
    105.}, (item: StringData, index: number) => index.toString())
    106.}.cachedCount(5)
    107.}
    108.}
   
    110.@Component
    111.struct ChildComponent {
    112.@ObjectLink data: StringData
    113.build() {
    114.Column() {
    115.Text(this.data.message).fontSize(50)
    116..onAppear(() => {
    117.console.info("appear:" + this.data.message)
    118.})
    119.Image(this.data.imgSrc)
    120..width(500)
    121..height(200)
    122.}.margin({ left: 10, right: 10 })
    123.}
    124.}
图11 修复LazyForEach仅改变笔墨但是图片闪烁问题
https://i-blog.csdnimg.cn/blog_migrate/796345bdd052f04827aef5992b6a4a98.gif@ObjectLink属性变化UI未更新
   
    1.class BasicDataSource implements IDataSource {
    2.private listeners: DataChangeListener[] = [];
    3.private originDataArray: StringData[] = [];
   
    5.public totalCount(): number {
    6.return 0;
    7.}
   
    9.public getData(index: number): StringData {
    10.return this.originDataArray;
    11.}
   
    13.registerDataChangeListener(listener: DataChangeListener): void {
    14.if (this.listeners.indexOf(listener) < 0) {
    15.console.info('add listener');
    16.this.listeners.push(listener);
    17.}
    18.}
   
    20.unregisterDataChangeListener(listener: DataChangeListener): void {
    21.const pos = this.listeners.indexOf(listener);
    22.if (pos >= 0) {
    23.console.info('remove listener');
    24.this.listeners.splice(pos, 1);
    25.}
    26.}
   
    28.notifyDataReload(): void {
    29.this.listeners.forEach(listener => {
    30.listener.onDataReloaded();
    31.})
    32.}
   
    34.notifyDataAdd(index: number): void {
    35.this.listeners.forEach(listener => {
    36.listener.onDataAdd(index);
    37.})
    38.}
   
    40.notifyDataChange(index: number): void {
    41.this.listeners.forEach(listener => {
    42.listener.onDataChange(index);
    43.})
    44.}
   
    46.notifyDataDelete(index: number): void {
    47.this.listeners.forEach(listener => {
    48.listener.onDataDelete(index);
    49.})
    50.}
    51.}
   
    53.class MyDataSource extends BasicDataSource {
    54.private dataArray: StringData[] = [];
   
    56.public totalCount(): number {
    57.return this.dataArray.length;
    58.}
   
    60.public getData(index: number): StringData {
    61.return this.dataArray;
    62.}
   
    64.public addData(index: number, data: StringData): void {
    65.this.dataArray.splice(index, 0, data);
    66.this.notifyDataAdd(index);
    67.}
   
    69.public pushData(data: StringData): void {
    70.this.dataArray.push(data);
    71.this.notifyDataAdd(this.dataArray.length - 1);
    72.}
    73.}
   
    75.@Observed
    76.class StringData {
    77.message: NestedString;
    78.constructor(message: NestedString) {
    79.this.message = message;
    80.}
    81.}
   
    83.@Observed
    84.class NestedString {
    85.message: string;
    86.constructor(message: string) {
    87.this.message = message;
    88.}
    89.}
   
    91.@Entry
    92.@Component
    93.struct MyComponent {
    94.private moved: number[] = [];
    95.@State data: MyDataSource = new MyDataSource();
   
    97.aboutToAppear() {
    98.for (let i = 0; i <= 20; i++) {
    99.this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
    100.}
    101.}
   
    103.build() {
    104.List({ space: 3 }) {
    105.LazyForEach(this.data, (item: StringData, index: number) => {
    106.ListItem() {
    107.ChildComponent({data: item})
    108.}
    109..onClick(() => {
    110.item.message.message += '0';
    111.})
    112.}, (item: StringData, index: number) => item.toString() + index.toString())
    113.}.cachedCount(5)
    114.}
    115.}
   
    117.@Component
    118.struct ChildComponent {
    119.@ObjectLink data: StringData
    120.build() {
    121.Row() {
    122.Text(this.data.message.message).fontSize(50)
    123..onAppear(() => {
    124.console.info("appear:" + this.data.message.message)
    125.})
    126.}.margin({ left: 10, right: 10 })
    127.}
    128.}
图12 ObjectLink属性变化后UI未更新
https://i-blog.csdnimg.cn/blog_migrate/c46fff11430aa5a46d619942ff7a1001.gif
@ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体请检察@ObjectLink与@Observed的具体使用方法和限定条件。
修复代码如下所示。
    1.class BasicDataSource implements IDataSource {
    2.private listeners: DataChangeListener[] = [];
    3.private originDataArray: StringData[] = [];
   
    5.public totalCount(): number {
    6.return 0;
    7.}
   
    9.public getData(index: number): StringData {
    10.return this.originDataArray;
    11.}
   
    13.registerDataChangeListener(listener: DataChangeListener): void {
    14.if (this.listeners.indexOf(listener) < 0) {
    15.console.info('add listener');
    16.this.listeners.push(listener);
    17.}
    18.}
   
    20.unregisterDataChangeListener(listener: DataChangeListener): void {
    21.const pos = this.listeners.indexOf(listener);
    22.if (pos >= 0) {
    23.console.info('remove listener');
    24.this.listeners.splice(pos, 1);
    25.}
    26.}
   
    28.notifyDataReload(): void {
    29.this.listeners.forEach(listener => {
    30.listener.onDataReloaded();
    31.})
    32.}
   
    34.notifyDataAdd(index: number): void {
    35.this.listeners.forEach(listener => {
    36.listener.onDataAdd(index);
    37.})
    38.}
   
    40.notifyDataChange(index: number): void {
    41.this.listeners.forEach(listener => {
    42.listener.onDataChange(index);
    43.})
    44.}
   
    46.notifyDataDelete(index: number): void {
    47.this.listeners.forEach(listener => {
    48.listener.onDataDelete(index);
    49.})
    50.}
    51.}
   
    53.class MyDataSource extends BasicDataSource {
    54.private dataArray: StringData[] = [];
   
    56.public totalCount(): number {
    57.return this.dataArray.length;
    58.}
   
    60.public getData(index: number): StringData {
    61.return this.dataArray;
    62.}
   
    64.public addData(index: number, data: StringData): void {
    65.this.dataArray.splice(index, 0, data);
    66.this.notifyDataAdd(index);
    67.}
   
    69.public pushData(data: StringData): void {
    70.this.dataArray.push(data);
    71.this.notifyDataAdd(this.dataArray.length - 1);
    72.}
    73.}
   
    75.@Observed
    76.class StringData {
    77.message: NestedString;
    78.constructor(message: NestedString) {
    79.this.message = message;
    80.}
    81.}
   
    83.@Observed
    84.class NestedString {
    85.message: string;
    86.constructor(message: string) {
    87.this.message = message;
    88.}
    89.}
   
    91.@Entry
    92.@Component
    93.struct MyComponent {
    94.private moved: number[] = [];
    95.@State data: MyDataSource = new MyDataSource();
   
    97.aboutToAppear() {
    98.for (let i = 0; i <= 20; i++) {
    99.this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
    100.}
    101.}
   
    103.build() {
    104.List({ space: 3 }) {
    105.LazyForEach(this.data, (item: StringData, index: number) => {
    106.ListItem() {
    107.ChildComponent({data: item})
    108.}
    109..onClick(() => {
    110.item.message = new NestedString(item.message.message + '0');
    111.})
    112.}, (item: StringData, index: number) => item.toString() + index.toString())
    113.}.cachedCount(5)
    114.}
    115.}
   
    117.@Component
    118.struct ChildComponent {
    119.@ObjectLink data: StringData
    120.build() {
    121.Row() {
    122.Text(this.data.message.message).fontSize(50)
    123..onAppear(() => {
    124.console.info("appear:" + this.data.message.message)
    125.})
    126.}.margin({ left: 10, right: 10 })
    127.}
    128.}
图13 修复ObjectLink属性变化后UI更新
https://i-blog.csdnimg.cn/blog_migrate/f03d3cad14d84a8b9f1c2a41cea016d2.gif

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: (五)鸿蒙HarmonyOS主力开发语言ArkTS-数据懒加载(LazyForEach)