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

系列文章目录

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



LazyForEach:数据懒加载

LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。
在这里插入图片描述

接口描述


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[index];
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[index];
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正常首次渲染

  • 键值相同时错误渲染

当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,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[index];
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[index];
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存在相同键值

  • 非首次渲染

当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[index];
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[index];
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添加数据


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[index];
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[index];
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删除数据


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[index];
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[index];
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改变单个数据


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[index];
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[index];
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改变多个数据

若仅靠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[index];
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[index];
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改变数据子属性


常见使用问题

  • 渲染结果非预期

    
    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[index];
    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[index];
    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删除数据非预期


    当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其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[index];
    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[index];
    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删除数据非预期


重渲染时图片闪烁

  
  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[index];
  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[index];
  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仅改变文字但是图片闪烁问题

在我们点击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[index];
    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[index];
    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仅改变文字但是图片闪烁问题

@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[index];
    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[index];
    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未更新


@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[index];
    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[index];
    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更新

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值