首页 > 其他分享 >[三、渲染控制法]4. LazyForEach:数据懒加载

[三、渲染控制法]4. LazyForEach:数据懒加载

时间:2024-08-05 14:27:51浏览次数:6  
标签:index void number LazyForEach listener listeners 控制法 data 加载

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

接口描述

LazyForEach(
    dataSource: IDataSource,             // 需要进行数据迭代的数据源
    itemGenerator: (item: any, index: number) => void,  // 子组件生成函数
    keyGenerator?: (item: any, index: number) => string // 键值生成函数
): 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类型说明

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

接口声明

参数类型

说明

totalCount(): number

-

获得数据总数。

getData(index: number): any

number

获取索引值index对应的数据。

index:获取数据对应的索引值。

registerDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注册数据改变的监听器。

listener:数据变化监听器

unregisterDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注销数据改变的监听器。

listener:数据变化监听器

DataChangeListener类型说明

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

接口声明

参数类型

说明

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首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。

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

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): string {
    return this.originDataArray[index];
  }

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

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

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

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

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

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

  // 通知LazyForEach组件将from索引和to索引处的子组件进行交换
  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

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

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

  public getData(index: number): string {
    return this.dataArray[index];
  }

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

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

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

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

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info("appear:" + item)
              })
          }.margin({ left: 10, right: 10 })
        }
      }, (item: string) => item)
    }.cachedCount(5)
  }
}

在上述代码中,键值生成规则是keyGenerator函数的返回值item。在LazyForEach循环渲染时,其为数据源数组项依次生成键值Hello 0、Hello 1 ... Hello 20,并创建对应的ListItem子组件渲染到界面上。

运行效果如下图所示。

图1 LazyForEach正常首次渲染

非首次渲染

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

  • 添加数据
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): string {
    return this.originDataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

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

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

  public getData(index: number): string {
    return this.dataArray[index];
  }

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

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

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

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

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info("appear:" + item)
              })
          }.margin({ left: 10, right: 10 })
        }
        .onClick(() => {
          // 点击追加子组件
          this.data.pushData(`Hello ${this.data.totalCount()}`);
        })
      }, (item: string) => item)
    }.cachedCount(5)
  }
}

当我们点击LazyForEach的子组件时,首先调用数据源data的pushData方法,该方法会在数据源末尾添加数据并调用notifyDataAdd方法。在notifyDataAdd方法内会又调用listener.onDataAdd方法,该方法会通知LazyForEach在该处有数据添加,LazyForEach便会在该索引处新建子组件。

运行效果如下图所示。

图2 LazyForEach添加数据

  • 删除数据
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): string {
    return this.originDataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

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

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

  public getData(index: number): string {
    return this.dataArray[index];
  }

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

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

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

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource();

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

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string, index: number) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info("appear:" + item)
              })
          }.margin({ left: 10, right: 10 })
        }
        .onClick(() => {
          // 点击删除子组件
          this.data.deleteData(this.data.dataArray.indexOf(item));
        })
      }, (item: string) => item)
    }.cachedCount(5)
  }
}

当我们点击LazyForEach的子组件时,首先调用数据源data的deleteData方法,该方法会删除数据源对应索引处的数据并调用notifyDataDelete方法。在notifyDataDelete方法内会又调用listener.onDataDelete方法,该方法会通知LazyForEach在该处有数据删除,LazyForEach便会在该索引处删除对应子组件。

运行效果如下图所示。

图3 LazyForEach删除数据

  • 交换数据
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = [];
  private originDataArray: string[] = [];

  public totalCount(): number {
    return 0;
  }

  public getData(index: number): string {
    return this.originDataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }
}

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

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

  public getData(index: number): string {
    return this.dataArray[index];
  }

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

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

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

  public moveData(from: number, to: number): void {
    let temp: string = this.dataArray[from];
    this.dataArray[from] = this.dataArray[to];
    this.dataArray[to] = temp;
    this.notifyDataMove(from, to);
  }
}

@Entry
@Component
struct MyComponent {
  private moved: number[] = [];
  private data: MyDataSource = new MyDataSource();

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

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string, index: number) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info("appear:" + item)
              })
          }.margin({ left: 10, right: 10 })
        }
        .onClick(() => {
          this.moved.push(this.data.dataArray.indexOf(item));
          if (this.moved.length === 2) {
            // 点击移动子组件
            this.data.moveData(this.moved[0], this.moved[1]);
            this.moved = [];
          }
        })
      }, (item: string) => item)
    }.cachedCount(5)
  }
}

当我们首次点击LazyForEach的子组件时,在moved成员变量内存入要移动的数据索引,再次点击LazyForEach另一个子组件时,我们将首次点击的子组件移到此处。调用数据源data的moveData方法,该方法会将数据源对应数据移动到预期的位置并调用notifyDatMove方法。在notifyDataMove方法内会又调用listener.onDataMove方法,该方法通知LazyForEach在该处有数据需要移动,LazyForEach便会将from和to索引处的子组件进行位置调换。

运行效果如下图所示。

图4 LazyForEach交换数据

  • 改变单个数据    
    1. class BasicDataSource implements IDataSource {
    2. private listeners: DataChangeListener[] = [];
    3. private originDataArray: string[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): string {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: string[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): string {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: string): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: string): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. public deleteData(index: number): void {
    66. this.dataArray.splice(index, 1);
    67. this.notifyDataDelete(index);
    68. }
    69. public changeData(index: number, data: string): void {
    70. this.dataArray.splice(index, 1, data);
    71. this.notifyDataChange(index);
    72. }
    73. }
    74. @Entry
    75. @Component
    76. struct MyComponent {
    77. private moved: number[] = [];
    78. private data: MyDataSource = new MyDataSource();
    79. aboutToAppear() {
    80. for (let i = 0; i <= 20; i++) {
    81. this.data.pushData(`Hello ${i}`)
    82. }
    83. }
    84. build() {
    85. List({ space: 3 }) {
    86. LazyForEach(this.data, (item: string, index: number) => {
    87. ListItem() {
    88. Row() {
    89. Text(item).fontSize(50)
    90. .onAppear(() => {
    91. console.info("appear:" + item)
    92. })
    93. }.margin({ left: 10, right: 10 })
    94. }
    95. .onClick(() => {
    96. this.data.changeData(index, item + '00');
    97. })
    98. }, (item: string) => item)
    99. }.cachedCount(5)
    100. }
    101. }

当我们点击LazyForEach的子组件时,首先改变当前数据,然后调用数据源data的changeData方法,在该方法内会调用notifyDataChange方法。在notifyDataChange方法内会又调用listener.onDataChange方法,该方法通知LazyForEach组件该处有数据发生变化,LazyForEach便会在对应索引处重建子组件。

运行效果如下图所示。

图5 LazyForEach改变单个数据

  • 改变多个数据    
    1. class BasicDataSource implements IDataSource {
    2. private listeners: DataChangeListener[] = [];
    3. private originDataArray: string[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): string {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: string[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): string {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: string): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: string): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. public deleteData(index: number): void {
    66. this.dataArray.splice(index, 1);
    67. this.notifyDataDelete(index);
    68. }
    69. public changeData(index: number): void {
    70. this.notifyDataChange(index);
    71. }
    72. public reloadData(): void {
    73. this.notifyDataReload();
    74. }
    75. public modifyAllData(): void {
    76. this.dataArray = this.dataArray.map((item: string) => {
    77. return item + '0';
    78. })
    79. }
    80. }
    81. @Entry
    82. @Component
    83. struct MyComponent {
    84. private moved: number[] = [];
    85. private data: MyDataSource = new MyDataSource();
    86. aboutToAppear() {
    87. for (let i = 0; i <= 20; i++) {
    88. this.data.pushData(`Hello ${i}`)
    89. }
    90. }
    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. this.data.modifyAllData();
    104. this.data.reloadData();
    105. })
    106. }, (item: string) => item)
    107. }.cachedCount(5)
    108. }
    109. }

当我们点击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[] = [];
  4. public totalCount(): number {
  5. return 0;
  6. }
  7. public getData(index: number): StringData {
  8. return this.originDataArray[index];
  9. }
  10. registerDataChangeListener(listener: DataChangeListener): void {
  11. if (this.listeners.indexOf(listener) < 0) {
  12. console.info('add listener');
  13. this.listeners.push(listener);
  14. }
  15. }
  16. unregisterDataChangeListener(listener: DataChangeListener): void {
  17. const pos = this.listeners.indexOf(listener);
  18. if (pos >= 0) {
  19. console.info('remove listener');
  20. this.listeners.splice(pos, 1);
  21. }
  22. }
  23. notifyDataReload(): void {
  24. this.listeners.forEach(listener => {
  25. listener.onDataReloaded();
  26. })
  27. }
  28. notifyDataAdd(index: number): void {
  29. this.listeners.forEach(listener => {
  30. listener.onDataAdd(index);
  31. })
  32. }
  33. notifyDataChange(index: number): void {
  34. this.listeners.forEach(listener => {
  35. listener.onDataChange(index);
  36. })
  37. }
  38. notifyDataDelete(index: number): void {
  39. this.listeners.forEach(listener => {
  40. listener.onDataDelete(index);
  41. })
  42. }
  43. notifyDataMove(from: number, to: number): void {
  44. this.listeners.forEach(listener => {
  45. listener.onDataMove(from, to);
  46. })
  47. }
  48. }
  49. class MyDataSource extends BasicDataSource {
  50. private dataArray: StringData[] = [];
  51. public totalCount(): number {
  52. return this.dataArray.length;
  53. }
  54. public getData(index: number): StringData {
  55. return this.dataArray[index];
  56. }
  57. public addData(index: number, data: StringData): void {
  58. this.dataArray.splice(index, 0, data);
  59. this.notifyDataAdd(index);
  60. }
  61. public pushData(data: StringData): void {
  62. this.dataArray.push(data);
  63. this.notifyDataAdd(this.dataArray.length - 1);
  64. }
  65. }
  66. @Observed
  67. class StringData {
  68. message: string;
  69. constructor(message: string) {
  70. this.message = message;
  71. }
  72. }
  73. @Entry
  74. @Component
  75. struct MyComponent {
  76. private moved: number[] = [];
  77. @State data: MyDataSource = new MyDataSource();
  78. aboutToAppear() {
  79. for (let i = 0; i <= 20; i++) {
  80. this.data.pushData(new StringData(`Hello ${i}`));
  81. }
  82. }
  83. build() {
  84. List({ space: 3 }) {
  85. LazyForEach(this.data, (item: StringData, index: number) => {
  86. ListItem() {
  87. ChildComponent({data: item})
  88. }
  89. .onClick(() => {
  90. item.message += '0';
  91. })
  92. }, (item: StringData, index: number) => index.toString())
  93. }.cachedCount(5)
  94. }
  95. }
  96. @Component
  97. struct ChildComponent {
  98. @ObjectLink data: StringData
  99. build() {
  100. Row() {
  101. Text(this.data.message).fontSize(50)
  102. .onAppear(() => {
  103. console.info("appear:" + this.data.message)
  104. })
  105. }.margin({ left: 10, right: 10 })
  106. }
  107. }

此时点击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[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): string {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: string[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): string {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: string): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: string): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. public deleteData(index: number): void {
    66. this.dataArray.splice(index, 1);
    67. this.notifyDataDelete(index);
    68. }
    69. }
    70. @Entry
    71. @Component
    72. struct MyComponent {
    73. private data: MyDataSource = new MyDataSource();
    74. aboutToAppear() {
    75. for (let i = 0; i <= 20; i++) {
    76. this.data.pushData(`Hello ${i}`)
    77. }
    78. }
    79. build() {
    80. List({ space: 3 }) {
    81. LazyForEach(this.data, (item: string, index: number) => {
    82. ListItem() {
    83. Row() {
    84. Text(item).fontSize(50)
    85. .onAppear(() => {
    86. console.info("appear:" + item)
    87. })
    88. }.margin({ left: 10, right: 10 })
    89. }
    90. .onClick(() => {
    91. // 点击删除子组件
    92. this.data.deleteData(index);
    93. })
    94. }, (item: string) => item)
    95. }.cachedCount(5)
    96. }
    97. }

    图8 LazyForEach删除数据非预期

    当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其index均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的index,其itemGenerator中的index并没有发生变化,所以删除结果和预期不符。

    修复代码如下所示。

       
    1. class BasicDataSource implements IDataSource {
    2. private listeners: DataChangeListener[] = [];
    3. private originDataArray: string[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): string {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: string[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): string {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: string): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: string): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. public deleteData(index: number): void {
    66. this.dataArray.splice(index, 1);
    67. this.notifyDataDelete(index);
    68. }
    69. public reloadData(): void {
    70. this.notifyDataReload();
    71. }
    72. }
    73. @Entry
    74. @Component
    75. struct MyComponent {
    76. private data: MyDataSource = new MyDataSource();
    77. aboutToAppear() {
    78. for (let i = 0; i <= 20; i++) {
    79. this.data.pushData(`Hello ${i}`)
    80. }
    81. }
    82. build() {
    83. List({ space: 3 }) {
    84. LazyForEach(this.data, (item: string, index: number) => {
    85. ListItem() {
    86. Row() {
    87. Text(item).fontSize(50)
    88. .onAppear(() => {
    89. console.info("appear:" + item)
    90. })
    91. }.margin({ left: 10, right: 10 })
    92. }
    93. .onClick(() => {
    94. // 点击删除子组件
    95. this.data.deleteData(index);
    96. // 重置所有子组件的index索引
    97. this.data.reloadData();
    98. })
    99. }, (item: string, index: number) => item + index.toString())
    100. }.cachedCount(5)
    101. }
    102. }

    在删除一个数据项后调用reloadData方法,重建后面的数据项,以达到更新index索引的目的。

    图9 修复LazyForEach删除数据非预期

  • 重渲染时图片闪烁    
    1. class BasicDataSource implements IDataSource {
    2. private listeners: DataChangeListener[] = [];
    3. private originDataArray: StringData[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): StringData {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: StringData[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): StringData {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: StringData): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: StringData): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. public reloadData(): void {
    66. this.notifyDataReload();
    67. }
    68. }
    69. class StringData {
    70. message: string;
    71. imgSrc: Resource;
    72. constructor(message: string, imgSrc: Resource) {
    73. this.message = message;
    74. this.imgSrc = imgSrc;
    75. }
    76. }
    77. @Entry
    78. @Component
    79. struct MyComponent {
    80. private moved: number[] = [];
    81. private data: MyDataSource = new MyDataSource();
    82. aboutToAppear() {
    83. for (let i = 0; i <= 20; i++) {
    84. this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
    85. }
    86. }
    87. build() {
    88. List({ space: 3 }) {
    89. LazyForEach(this.data, (item: StringData, index: number) => {
    90. ListItem() {
    91. Column() {
    92. Text(item.message).fontSize(50)
    93. .onAppear(() => {
    94. console.info("appear:" + item.message)
    95. })
    96. Image(item.imgSrc)
    97. .width(500)
    98. .height(200)
    99. }.margin({ left: 10, right: 10 })
    100. }
    101. .onClick(() => {
    102. item.message += '00';
    103. this.data.reloadData();
    104. })
    105. }, (item: StringData, index: number) => JSON.stringify(item))
    106. }.cachedCount(5)
    107. }
    108. }

    图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[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): StringData {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: StringData[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): StringData {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: StringData): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: StringData): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. }
    66. @Observed
    67. class StringData {
    68. message: string;
    69. imgSrc: Resource;
    70. constructor(message: string, imgSrc: Resource) {
    71. this.message = message;
    72. this.imgSrc = imgSrc;
    73. }
    74. }
    75. @Entry
    76. @Component
    77. struct MyComponent {
    78. @State data: MyDataSource = new MyDataSource();
    79. aboutToAppear() {
    80. for (let i = 0; i <= 20; i++) {
    81. this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
    82. }
    83. }
    84. build() {
    85. List({ space: 3 }) {
    86. LazyForEach(this.data, (item: StringData, index: number) => {
    87. ListItem() {
    88. ChildComponent({data: item})
    89. }
    90. .onClick(() => {
    91. item.message += '0';
    92. })
    93. }, (item: StringData, index: number) => index.toString())
    94. }.cachedCount(5)
    95. }
    96. }
    97. @Component
    98. struct ChildComponent {
    99. @ObjectLink data: StringData
    100. build() {
    101. Column() {
    102. Text(this.data.message).fontSize(50)
    103. .onAppear(() => {
    104. console.info("appear:" + this.data.message)
    105. })
    106. Image(this.data.imgSrc)
    107. .width(500)
    108. .height(200)
    109. }.margin({ left: 10, right: 10 })
    110. }
    111. }

    图11 修复LazyForEach仅改变文字但是图片闪烁问题

  • @ObjectLink属性变化UI未更新    
    1. class BasicDataSource implements IDataSource {
    2. private listeners: DataChangeListener[] = [];
    3. private originDataArray: StringData[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): StringData {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: StringData[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): StringData {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: StringData): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: StringData): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. }
    66. @Observed
    67. class StringData {
    68. message: NestedString;
    69. constructor(message: NestedString) {
    70. this.message = message;
    71. }
    72. }
    73. @Observed
    74. class NestedString {
    75. message: string;
    76. constructor(message: string) {
    77. this.message = message;
    78. }
    79. }
    80. @Entry
    81. @Component
    82. struct MyComponent {
    83. private moved: number[] = [];
    84. @State data: MyDataSource = new MyDataSource();
    85. aboutToAppear() {
    86. for (let i = 0; i <= 20; i++) {
    87. this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
    88. }
    89. }
    90. build() {
    91. List({ space: 3 }) {
    92. LazyForEach(this.data, (item: StringData, index: number) => {
    93. ListItem() {
    94. ChildComponent({data: item})
    95. }
    96. .onClick(() => {
    97. item.message.message += '0';
    98. })
    99. }, (item: StringData, index: number) => JSON.stringify(item) + index.toString())
    100. }.cachedCount(5)
    101. }
    102. }
    103. @Component
    104. struct ChildComponent {
    105. @ObjectLink data: StringData
    106. build() {
    107. Row() {
    108. Text(this.data.message.message).fontSize(50)
    109. .onAppear(() => {
    110. console.info("appear:" + this.data.message.message)
    111. })
    112. }.margin({ left: 10, right: 10 })
    113. }
    114. }

    图12 ObjectLink属性变化后UI未更新

    @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体请查看@ObjectLink与@Observed的详细使用方法和限制条件。

    修复代码如下所示。

       
    1. class BasicDataSource implements IDataSource {
    2. private listeners: DataChangeListener[] = [];
    3. private originDataArray: StringData[] = [];
    4. public totalCount(): number {
    5. return 0;
    6. }
    7. public getData(index: number): StringData {
    8. return this.originDataArray[index];
    9. }
    10. registerDataChangeListener(listener: DataChangeListener): void {
    11. if (this.listeners.indexOf(listener) < 0) {
    12. console.info('add listener');
    13. this.listeners.push(listener);
    14. }
    15. }
    16. unregisterDataChangeListener(listener: DataChangeListener): void {
    17. const pos = this.listeners.indexOf(listener);
    18. if (pos >= 0) {
    19. console.info('remove listener');
    20. this.listeners.splice(pos, 1);
    21. }
    22. }
    23. notifyDataReload(): void {
    24. this.listeners.forEach(listener => {
    25. listener.onDataReloaded();
    26. })
    27. }
    28. notifyDataAdd(index: number): void {
    29. this.listeners.forEach(listener => {
    30. listener.onDataAdd(index);
    31. })
    32. }
    33. notifyDataChange(index: number): void {
    34. this.listeners.forEach(listener => {
    35. listener.onDataChange(index);
    36. })
    37. }
    38. notifyDataDelete(index: number): void {
    39. this.listeners.forEach(listener => {
    40. listener.onDataDelete(index);
    41. })
    42. }
    43. notifyDataMove(from: number, to: number): void {
    44. this.listeners.forEach(listener => {
    45. listener.onDataMove(from, to);
    46. })
    47. }
    48. }
    49. class MyDataSource extends BasicDataSource {
    50. private dataArray: StringData[] = [];
    51. public totalCount(): number {
    52. return this.dataArray.length;
    53. }
    54. public getData(index: number): StringData {
    55. return this.dataArray[index];
    56. }
    57. public addData(index: number, data: StringData): void {
    58. this.dataArray.splice(index, 0, data);
    59. this.notifyDataAdd(index);
    60. }
    61. public pushData(data: StringData): void {
    62. this.dataArray.push(data);
    63. this.notifyDataAdd(this.dataArray.length - 1);
    64. }
    65. }
    66. @Observed
    67. class StringData {
    68. message: NestedString;
    69. constructor(message: NestedString) {
    70. this.message = message;
    71. }
    72. }
    73. @Observed
    74. class NestedString {
    75. message: string;
    76. constructor(message: string) {
    77. this.message = message;
    78. }
    79. }
    80. @Entry
    81. @Component
    82. struct MyComponent {
    83. private moved: number[] = [];
    84. @State data: MyDataSource = new MyDataSource();
    85. aboutToAppear() {
    86. for (let i = 0; i <= 20; i++) {
    87. this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
    88. }
    89. }
    90. build() {
    91. List({ space: 3 }) {
    92. LazyForEach(this.data, (item: StringData, index: number) => {
    93. ListItem() {
    94. ChildComponent({data: item})
    95. }
    96. .onClick(() => {
    97. item.message = new NestedString(item.message.message + '0');
    98. })
    99. }, (item: StringData, index: number) => JSON.stringify(item) + index.toString())
    100. }.cachedCount(5)
    101. }
    102. }
    103. @Component
    104. struct ChildComponent {
    105. @ObjectLink data: StringData
    106. build() {
    107. Row() {
    108. Text(this.data.message.message).fontSize(50)
    109. .onAppear(() => {
    110. console.info("appear:" + this.data.message.message)
    111. })
    112. }.margin({ left: 10, right: 10 })
    113. }
    114. }

    图13 修复ObjectLink属性变化后UI更新

标签:index,void,number,LazyForEach,listener,listeners,控制法,data,加载
From: https://www.cnblogs.com/strengthen/p/18325958

相关文章

  • 继承的特点注意事项以及类的初始化顺序和加载顺序day08
    继承的好处提高了代码的复用性多个类相同的成员可以放到同一个类中提高了代码的维护性如果功能的代码需要修改,修改一处即可让类与类之间产生了关系,是多态的前提其实这也是继承的一个弊端:类的耦合性很强......
  • 【转载】ubuntu用户/linux用户登录后没有自动加载.bashrc
    版权声明:本文为CSDN博主「安安爸Chris」的原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/mimiduck/article/details/120041171今天遇到一个问题,linux下某用户登陆后无法加在其自身的.bashrc,通过source.bashrc发现......
  • 七、分散加载说明
    分散加载说明以GD32F103ZE为例,分别用Keil、IAR和EmbeddedBuilder工具实现:将函数放置某个地址、将常量放置某个地址、将函数放在RAM中运行的三种效果。1、将led_toggle()函数放在0x08040000地址后。2、将tempbuf[1024]常量放在0x08020000地址后。3、将voidled_flow(void)......
  • c动态加载c/c++ so并调用其中的函数或者子类实现
    在不少服务器应用中,会采用插件化或者模块化的体系实现具体的业务功能,比如mysql支持插件化体系,nginx采用模块化体系。总得来说,很多时候,因为扩展性,系统会采用动态加载so的方式扩展业务功能,而主框架不需要每次新增功能就不得不重新编译,很多时候,对于二进制发行的应用来说,不可能这......
  • Java中的类加载机制与自定义类加载器设计
    Java中的类加载机制与自定义类加载器设计大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们来探讨Java中的类加载机制与自定义类加载器设计。Java的类加载机制是Java虚拟机(JVM)运行时系统的基础之一,了解其工作原理以及如何设计自定义类加载器,对......
  • idea忽略.class、.idea文件和target目录,以及爆红jar包无法加载修复
    一、添加忽略文件的地方首先打开设置,然后找到下面这个位置然后添加对应类型(千万别添加.class,否则和我一样踩坑,方法二可恢复)。整个项目会重新加载二、修复jar包爆红然后整个项目全部爆红,是因为你忽略了.class,你删除刚才新增的忽略.class,然后修改下面这个地方。加......
  • 类加载器和双亲委派机制
    什么是类加载器?类加载器是Jvm的重要组成之一(类加载器、运行时数据区、执行引擎、本地库接口、本地方法库),负责读取java字节码并将其加载到Jvm中的组件类加载器的分类Java中的类加载器可以分为以下几种:1.启动类加载器(BootstrapClassLoader)定义:这是最顶层的类加载器,用于加......
  • 在新页面却加载旧页面的接口
    问题:使用甘特图gantt时,做了一个功能,双击甘特图数据能进行搜索详细情况//3.7双击显示明细gantt.config.details_on_dblclick=true;//监视if(this.eventId){gantt.detachEvent(this.eventId);//先移除之前的事件绑定}thi......
  • 如何在python中通过requests和opencv加载uint16 png文件
    我正在尝试从URL自动加载图像,然后将其加载到numpy矩阵。为此,我需要使用requests和opencv库。对于像uint8这样编码的标准图像,它以正确的方式工作,并且由于值溢出而损坏了uint16图像。这是我现在正在使用的一个简单的最小代码:importrequestsimportcv2importnumpy......
  • 运行期加载时共享库路径搜索优先级实验
    目录前言实验环境目录说明单独测试不配置路径默认路径ld.so.cacheRUNPATHLD_LIBRARY_PATHRPATH优先级测试附录库文件源码主程序源码makefile脚本run_nonerun_defaultrun_ld_so_cacherun_runpathrun_ld_library_pathrun_rpathrun_cmp_all前言《共享库链接和加载时的路径搜索优先......