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

打印 上一主题 下一主题

主题 807|帖子 807|积分 2421

系列文章目录

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


  

LazyForEach:数据懒加载

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

接口描述

  1. 1.  LazyForEach(
  2. 2.      dataSource: IDataSource,             // 需要进行数据迭代的数据源
  3. 3.      itemGenerator: (item: any, index: number) => void,  // 子组件生成函数
  4. 4.      keyGenerator?: (item: any, index: number) => string // 键值生成函数
  5. 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. 1.  interface IDataSource {
  2. 2.      totalCount(): number; // 获得数据总数
  3. 3.      getData(index: number): Object; // 获取索引值对应的数据
  4. 4.      registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
  5. 5.      unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
  6. 6.  }
复制代码
接口声明
参数类型
说明
totalCount(): number
-
获得数据总数。
getData(index: number): any
number
获取索引值index对应的数据。
index:获取数据对应的索引值。
registerDataChangeListener(listenerataChangeListener): void
DataChangeListener
注册数据改变的监听器。
listener:数据变化监听器
unregisterDataChangeListener(listenerataChangeListener): void
DataChangeListener
注销数据改变的监听器。
listener:数据变化监听器
DataChangeListener类型说明

  1. 1.  interface DataChangeListener {
  2. 2.      onDataReloaded(): void; // 重新加载数据完成后调用
  3. 3.      onDataAdded(index: number): void; // 添加数据完成后调用
  4. 4.      onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
  5. 5.      onDataDeleted(index: number): void; // 删除数据完成后调用
  6. 6.      onDataChanged(index: number): void; // 改变数据完成后调用
  7. 7.      onDataAdd(index: number): void; // 添加数据完成后调用
  8. 8.      onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
  9. 9.      onDataDelete(index: number): void; // 删除数据完成后调用
  10. 10.      onDataChange(index: number): void; // 改变数据完成后调用
  11. 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. 1.  // Basic implementation of IDataSource to handle data listener
  2. 2.  class BasicDataSource implements IDataSource {
  3. 3.    private listeners: DataChangeListener[] = [];
  4. 4.    private originDataArray: string[] = [];
  5. 6.    public totalCount(): number {
  6. 7.      return 0;
  7. 8.    }
  8. 10.    public getData(index: number): string {
  9. 11.      return this.originDataArray[index];
  10. 12.    }
  11. 14.    // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  12. 15.    registerDataChangeListener(listener: DataChangeListener): void {
  13. 16.      if (this.listeners.indexOf(listener) < 0) {
  14. 17.        console.info('add listener');
  15. 18.        this.listeners.push(listener);
  16. 19.      }
  17. 20.    }
  18. 22.    // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  19. 23.    unregisterDataChangeListener(listener: DataChangeListener): void {
  20. 24.      const pos = this.listeners.indexOf(listener);
  21. 25.      if (pos >= 0) {
  22. 26.        console.info('remove listener');
  23. 27.        this.listeners.splice(pos, 1);
  24. 28.      }
  25. 29.    }
  26. 31.    // 通知LazyForEach组件需要重载所有子组件
  27. 32.    notifyDataReload(): void {
  28. 33.      this.listeners.forEach(listener => {
  29. 34.        listener.onDataReloaded();
  30. 35.      })
  31. 36.    }
  32. 38.    // 通知LazyForEach组件需要在index对应索引处添加子组件
  33. 39.    notifyDataAdd(index: number): void {
  34. 40.      this.listeners.forEach(listener => {
  35. 41.        listener.onDataAdd(index);
  36. 42.      })
  37. 43.    }
  38. 45.    // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  39. 46.    notifyDataChange(index: number): void {
  40. 47.      this.listeners.forEach(listener => {
  41. 48.        listener.onDataChange(index);
  42. 49.      })
  43. 50.    }
  44. 52.    // 通知LazyForEach组件需要在index对应索引处删除该子组件
  45. 53.    notifyDataDelete(index: number): void {
  46. 54.      this.listeners.forEach(listener => {
  47. 55.        listener.onDataDelete(index);
  48. 56.      })
  49. 57.    }
  50. 58.  }
  51. 60.  class MyDataSource extends BasicDataSource {
  52. 61.    private dataArray: string[] = [];
  53. 63.    public totalCount(): number {
  54. 64.      return this.dataArray.length;
  55. 65.    }
  56. 67.    public getData(index: number): string {
  57. 68.      return this.dataArray[index];
  58. 69.    }
  59. 71.    public addData(index: number, data: string): void {
  60. 72.      this.dataArray.splice(index, 0, data);
  61. 73.      this.notifyDataAdd(index);
  62. 74.    }
  63. 76.    public pushData(data: string): void {
  64. 77.      this.dataArray.push(data);
  65. 78.      this.notifyDataAdd(this.dataArray.length - 1);
  66. 79.    }
  67. 80.  }
  68. 82.  @Entry
  69. 83.  @Component
  70. 84.  struct MyComponent {
  71. 85.    private data: MyDataSource = new MyDataSource();
  72. 87.    aboutToAppear() {
  73. 88.      for (let i = 0; i <= 20; i++) {
  74. 89.        this.data.pushData(`Hello ${i}`)
  75. 90.      }
  76. 91.    }
  77. 93.    build() {
  78. 94.      List({ space: 3 }) {
  79. 95.        LazyForEach(this.data, (item: string) => {
  80. 96.          ListItem() {
  81. 97.            Row() {
  82. 98.              Text(item).fontSize(50)
  83. 99.                .onAppear(() => {
  84. 100.                  console.info("appear:" + item)
  85. 101.                })
  86. 102.            }.margin({ left: 10, right: 10 })
  87. 103.          }
  88. 104.        }, (item: string) => item)
  89. 105.      }.cachedCount(5)
  90. 106.    }
  91. 107.  }
复制代码
在上述代码中,键值天生规则是keyGenerator函数的返回值item。在LazyForEach循环渲染时,其为数据源数组项依次天生键值Hello 0、Hello 1 … Hello 20,并创建对应的ListItem子组件渲染到界面上。
运行效果如下图所示。
图1 LazyForEach正常首次渲染



  • 键值相同时错误渲染
当不同数据项天生的键值相同时,框架的行为是不可猜测的。例如,在以下代码中,LazyForEach渲染的数据项键值均相同,在滑动过程中,LazyForEach会对划入划出当前页面的子组件举行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架大概存在取用缓存错误的情况,导致子组件渲染有问题。
  1. 1.  class BasicDataSource implements IDataSource {
  2. 2.  private listeners: DataChangeListener[] = [];
  3. 3.  private originDataArray: string[] = [];
  4. 5.  public totalCount(): number {
  5. 6.  return 0;
  6. 7.  }
  7. 9.  public getData(index: number): string {
  8. 10.  return this.originDataArray[index];
  9. 11.  }
  10. 13.  registerDataChangeListener(listener: DataChangeListener): void {
  11. 14.  if (this.listeners.indexOf(listener) < 0) {
  12. 15.  console.info('add listener');
  13. 16.  this.listeners.push(listener);
  14. 17.  }
  15. 18.  }
  16. 20.  unregisterDataChangeListener(listener: DataChangeListener): void {
  17. 21.  const pos = this.listeners.indexOf(listener);
  18. 22.  if (pos >= 0) {
  19. 23.  console.info('remove listener');
  20. 24.  this.listeners.splice(pos, 1);
  21. 25.  }
  22. 26.  }
  23. 28.  notifyDataReload(): void {
  24. 29.  this.listeners.forEach(listener => {
  25. 30.  listener.onDataReloaded();
  26. 31.  })
  27. 32.  }
  28. 34.  notifyDataAdd(index: number): void {
  29. 35.  this.listeners.forEach(listener => {
  30. 36.  listener.onDataAdd(index);
  31. 37.  })
  32. 38.  }
  33. 40.  notifyDataChange(index: number): void {
  34. 41.  this.listeners.forEach(listener => {
  35. 42.  listener.onDataChange(index);
  36. 43.  })
  37. 44.  }
  38. 46.  notifyDataDelete(index: number): void {
  39. 47.  this.listeners.forEach(listener => {
  40. 48.  listener.onDataDelete(index);
  41. 49.  })
  42. 50.  }
  43. 51.  }
  44. 53.  class MyDataSource extends BasicDataSource {
  45. 54.  private dataArray: string[] = [];
  46. 56.  public totalCount(): number {
  47. 57.  return this.dataArray.length;
  48. 58.  }
  49. 60.  public getData(index: number): string {
  50. 61.  return this.dataArray[index];
  51. 62.  }
  52. 64.  public addData(index: number, data: string): void {
  53. 65.  this.dataArray.splice(index, 0, data);
  54. 66.  this.notifyDataAdd(index);
  55. 67.  }
  56. 69.  public pushData(data: string): void {
  57. 70.  this.dataArray.push(data);
  58. 71.  this.notifyDataAdd(this.dataArray.length - 1);
  59. 72.  }
  60. 73.  }
  61. 75.  @Entry
  62. 76.  @Component
  63. 77.  struct MyComponent {
  64. 78.  private data: MyDataSource = new MyDataSource();
  65. 80.  aboutToAppear() {
  66. 81.  for (let i = 0; i <= 20; i++) {
  67. 82.  this.data.pushData(`Hello ${i}`)
  68. 83.  }
  69. 84.  }
  70. 86.  build() {
  71. 87.  List({ space: 3 }) {
  72. 88.  LazyForEach(this.data, (item: string) => {
  73. 89.  ListItem() {
  74. 90.  Row() {
  75. 91.  Text(item).fontSize(50)
  76. 92.  .onAppear(() => {
  77. 93.  console.info("appear:" + item)
  78. 94.  })
  79. 95.  }.margin({ left: 10, right: 10 })
  80. 96.  }
  81. 97.  }, (item: string) => 'same key')
  82. 98.  }.cachedCount(5)
  83. 99.  }
  84. 100.  }
复制代码
运行效果如下图所示。可以看到Hello 0在滑动过程中被错误渲染为Hello 13。
图2 LazyForEach存在相同键值



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


  • 添加数据
  1. 1.  class BasicDataSource implements IDataSource {
  2. 2.    private listeners: DataChangeListener[] = [];
  3. 3.    private originDataArray: string[] = [];
  4. 5.    public totalCount(): number {
  5. 6.      return 0;
  6. 7.    }
  7. 9.    public getData(index: number): string {
  8. 10.      return this.originDataArray[index];
  9. 11.    }
  10. 13.    registerDataChangeListener(listener: DataChangeListener): void {
  11. 14.      if (this.listeners.indexOf(listener) < 0) {
  12. 15.        console.info('add listener');
  13. 16.        this.listeners.push(listener);
  14. 17.      }
  15. 18.    }
  16. 20.    unregisterDataChangeListener(listener: DataChangeListener): void {
  17. 21.      const pos = this.listeners.indexOf(listener);
  18. 22.      if (pos >= 0) {
  19. 23.        console.info('remove listener');
  20. 24.        this.listeners.splice(pos, 1);
  21. 25.      }
  22. 26.    }
  23. 28.    notifyDataReload(): void {
  24. 29.      this.listeners.forEach(listener => {
  25. 30.        listener.onDataReloaded();
  26. 31.      })
  27. 32.    }
  28. 34.    notifyDataAdd(index: number): void {
  29. 35.      this.listeners.forEach(listener => {
  30. 36.        listener.onDataAdd(index);
  31. 37.      })
  32. 38.    }
  33. 40.    notifyDataChange(index: number): void {
  34. 41.      this.listeners.forEach(listener => {
  35. 42.        listener.onDataChange(index);
  36. 43.      })
  37. 44.    }
  38. 46.    notifyDataDelete(index: number): void {
  39. 47.      this.listeners.forEach(listener => {
  40. 48.        listener.onDataDelete(index);
  41. 49.      })
  42. 50.    }
  43. 51.  }
  44. 53.  class MyDataSource extends BasicDataSource {
  45. 54.    private dataArray: string[] = [];
  46. 56.    public totalCount(): number {
  47. 57.      return this.dataArray.length;
  48. 58.    }
  49. 60.    public getData(index: number): string {
  50. 61.      return this.dataArray[index];
  51. 62.    }
  52. 64.    public addData(index: number, data: string): void {
  53. 65.      this.dataArray.splice(index, 0, data);
  54. 66.      this.notifyDataAdd(index);
  55. 67.    }
  56. 69.    public pushData(data: string): void {
  57. 70.      this.dataArray.push(data);
  58. 71.      this.notifyDataAdd(this.dataArray.length - 1);
  59. 72.    }
  60. 73.  }
  61. 75.  @Entry
  62. 76.  @Component
  63. 77.  struct MyComponent {
  64. 78.    private data: MyDataSource = new MyDataSource();
  65. 80.    aboutToAppear() {
  66. 81.      for (let i = 0; i <= 20; i++) {
  67. 82.        this.data.pushData(`Hello ${i}`)
  68. 83.      }
  69. 84.    }
  70. 86.    build() {
  71. 87.      List({ space: 3 }) {
  72. 88.        LazyForEach(this.data, (item: string) => {
  73. 89.          ListItem() {
  74. 90.            Row() {
  75. 91.              Text(item).fontSize(50)
  76. 92.                .onAppear(() => {
  77. 93.                  console.info("appear:" + item)
  78. 94.                })
  79. 95.            }.margin({ left: 10, right: 10 })
  80. 96.          }
  81. 97.          .onClick(() => {
  82. 98.            // 点击追加子组件
  83. 99.            this.data.pushData(`Hello ${this.data.totalCount()}`);
  84. 100.          })
  85. 101.        }, (item: string) => item)
  86. 102.      }.cachedCount(5)
  87. 103.    }
  88. 104.  }
复制代码
当我们点击LazyForEach的子组件时,首先调用数据源data的pushData方法,该方法会在数据源末尾添加数据并调用notifyDataAdd方法。在notifyDataAdd方法内会又调用listener.onDataAdd方法,该方法会通知LazyForEach在该处有数据添加,LazyForEach便会在该索引处新建子组件。
运行效果如下图所示。
图3 LazyForEach添加数据

  1. 1.  class BasicDataSource implements IDataSource {
  2. 2.  private listeners: DataChangeListener[] = [];
  3. 3.  private originDataArray: string[] = [];
  4. 5.  public totalCount(): number {
  5. 6.  return 0;
  6. 7.  }
  7. 9.  public getData(index: number): string {
  8. 10.  return this.originDataArray[index];
  9. 11.  }
  10. 13.  registerDataChangeListener(listener: DataChangeListener): void {
  11. 14.  if (this.listeners.indexOf(listener) < 0) {
  12. 15.  console.info('add listener');
  13. 16.  this.listeners.push(listener);
  14. 17.  }
  15. 18.  }
  16. 20.  unregisterDataChangeListener(listener: DataChangeListener): void {
  17. 21.  const pos = this.listeners.indexOf(listener);
  18. 22.  if (pos >= 0) {
  19. 23.  console.info('remove listener');
  20. 24.  this.listeners.splice(pos, 1);
  21. 25.  }
  22. 26.  }
  23. 28.  notifyDataReload(): void {
  24. 29.  this.listeners.forEach(listener => {
  25. 30.  listener.onDataReloaded();
  26. 31.  })
  27. 32.  }
  28. 34.  notifyDataAdd(index: number): void {
  29. 35.  this.listeners.forEach(listener => {
  30. 36.  listener.onDataAdd(index);
  31. 37.  })
  32. 38.  }
  33. 40.  notifyDataChange(index: number): void {
  34. 41.  this.listeners.forEach(listener => {
  35. 42.  listener.onDataChange(index);
  36. 43.  })
  37. 44.  }
  38. 46.  notifyDataDelete(index: number): void {
  39. 47.  this.listeners.forEach(listener => {
  40. 48.  listener.onDataDelete(index);
  41. 49.  })
  42. 50.  }
  43. 51.  }
  44. 53.  class MyDataSource extends BasicDataSource {
  45. 54.  dataArray: string[] = [];
  46. 56.  public totalCount(): number {
  47. 57.  return this.dataArray.length;
  48. 58.  }
  49. 60.  public getData(index: number): string {
  50. 61.  return this.dataArray[index];
  51. 62.  }
  52. 64.  public addData(index: number, data: string): void {
  53. 65.  this.dataArray.splice(index, 0, data);
  54. 66.  this.notifyDataAdd(index);
  55. 67.  }
  56. 69.  public pushData(data: string): void {
  57. 70.  this.dataArray.push(data);
  58. 71.  this.notifyDataAdd(this.dataArray.length - 1);
  59. 72.  }
  60. 74.  public deleteData(index: number): void {
  61. 75.  this.dataArray.splice(index, 1);
  62. 76.  this.notifyDataDelete(index);
  63. 77.  }
  64. 78.  }
  65. 80.  @Entry
  66. 81.  @Component
  67. 82.  struct MyComponent {
  68. 83.  private data: MyDataSource = new MyDataSource();
  69. 85.  aboutToAppear() {
  70. 86.  for (let i = 0; i <= 20; i++) {
  71. 87.  this.data.pushData(`Hello ${i}`)
  72. 88.  }
  73. 89.  }
  74. 91.  build() {
  75. 92.  List({ space: 3 }) {
  76. 93.  LazyForEach(this.data, (item: string, index: number) => {
  77. 94.  ListItem() {
  78. 95.  Row() {
  79. 96.  Text(item).fontSize(50)
  80. 97.  .onAppear(() => {
  81. 98.  console.info("appear:" + item)
  82. 99.  })
  83. 100.  }.margin({ left: 10, right: 10 })
  84. 101.  }
  85. 102.  .onClick(() => {
  86. 103.  // 点击删除子组件
  87. 104.  this.data.deleteData(this.data.dataArray.indexOf(item));
  88. 105.  })
  89. 106.  }, (item: string) => item)
  90. 107.  }.cachedCount(5)
  91. 108.  }
  92. 109.  }
复制代码
当我们点击LazyForEach的子组件时,首先调用数据源data的deleteData方法,该方法会删除数据源对应索引处的数据并调用notifyDataDelete方法。在notifyDataDelete方法内会又调用listener.onDataDelete方法,该方法会通知LazyForEach在该处有数据删除,LazyForEach便会在该索引处删除对应子组件。
运行效果如下图所示。
图4 LazyForEach删除数据

  1. 1.  class BasicDataSource implements IDataSource {
  2. 2.  private listeners: DataChangeListener[] = [];
  3. 3.  private originDataArray: string[] = [];
  4. 5.  public totalCount(): number {
  5. 6.  return 0;
  6. 7.  }
  7. 9.  public getData(index: number): string {
  8. 10.  return this.originDataArray[index];
  9. 11.  }
  10. 13.  registerDataChangeListener(listener: DataChangeListener): void {
  11. 14.  if (this.listeners.indexOf(listener) < 0) {
  12. 15.  console.info('add listener');
  13. 16.  this.listeners.push(listener);
  14. 17.  }
  15. 18.  }
  16. 20.  unregisterDataChangeListener(listener: DataChangeListener): void {
  17. 21.  const pos = this.listeners.indexOf(listener);
  18. 22.  if (pos >= 0) {
  19. 23.  console.info('remove listener');
  20. 24.  this.listeners.splice(pos, 1);
  21. 25.  }
  22. 26.  }
  23. 28.  notifyDataReload(): void {
  24. 29.  this.listeners.forEach(listener => {
  25. 30.  listener.onDataReloaded();
  26. 31.  })
  27. 32.  }
  28. 34.  notifyDataAdd(index: number): void {
  29. 35.  this.listeners.forEach(listener => {
  30. 36.  listener.onDataAdd(index);
  31. 37.  })
  32. 38.  }
  33. 40.  notifyDataChange(index: number): void {
  34. 41.  this.listeners.forEach(listener => {
  35. 42.  listener.onDataChange(index);
  36. 43.  })
  37. 44.  }
  38. 46.  notifyDataDelete(index: number): void {
  39. 47.  this.listeners.forEach(listener => {
  40. 48.  listener.onDataDelete(index);
  41. 49.  })
  42. 50.  }
  43. 51.  }
  44. 53.  class MyDataSource extends BasicDataSource {
  45. 54.  private dataArray: string[] = [];
  46. 56.  public totalCount(): number {
  47. 57.  return this.dataArray.length;
  48. 58.  }
  49. 60.  public getData(index: number): string {
  50. 61.  return this.dataArray[index];
  51. 62.  }
  52. 64.  public addData(index: number, data: string): void {
  53. 65.  this.dataArray.splice(index, 0, data);
  54. 66.  this.notifyDataAdd(index);
  55. 67.  }
  56. 69.  public pushData(data: string): void {
  57. 70.  this.dataArray.push(data);
  58. 71.  this.notifyDataAdd(this.dataArray.length - 1);
  59. 72.  }
  60. 74.  public deleteData(index: number): void {
  61. 75.  this.dataArray.splice(index, 1);
  62. 76.  this.notifyDataDelete(index);
  63. 77.  }
  64. 79.  public changeData(index: number, data: string): void {
  65. 80.  this.dataArray.splice(index, 1, data);
  66. 81.  this.notifyDataChange(index);
  67. 82.  }
  68. 83.  }
  69. 85.  @Entry
  70. 86.  @Component
  71. 87.  struct MyComponent {
  72. 88.  private moved: number[] = [];
  73. 89.  private data: MyDataSource = new MyDataSource();
  74. 91.  aboutToAppear() {
  75. 92.  for (let i = 0; i <= 20; i++) {
  76. 93.  this.data.pushData(`Hello ${i}`)
  77. 94.  }
  78. 95.  }
  79. 98.  build() {
  80. 99.  List({ space: 3 }) {
  81. 100.  LazyForEach(this.data, (item: string, index: number) => {
  82. 101.  ListItem() {
  83. 102.  Row() {
  84. 103.  Text(item).fontSize(50)
  85. 104.  .onAppear(() => {
  86. 105.  console.info("appear:" + item)
  87. 106.  })
  88. 107.  }.margin({ left: 10, right: 10 })
  89. 108.  }
  90. 109.  .onClick(() => {
  91. 110.  this.data.changeData(index, item + '00');
  92. 111.  })
  93. 112.  }, (item: string) => item)
  94. 113.  }.cachedCount(5)
  95. 114.  }
  96. 115.  }
复制代码
当我们点击LazyForEach的子组件时,首先改变当前数据,然后调用数据源data的changeData方法,在该方法内会调用notifyDataChange方法。在notifyDataChange方法内会又调用listener.onDataChange方法,该方法通知LazyForEach组件该处有数据发生变化,LazyForEach便会在对应索引处重建子组件。
运行效果如下图所示。
图5 LazyForEach改变单个数据

  1. 1.  class BasicDataSource implements IDataSource {
  2. 2.  private listeners: DataChangeListener[] = [];
  3. 3.  private originDataArray: string[] = [];
  4. 5.  public totalCount(): number {
  5. 6.  return 0;
  6. 7.  }
  7. 9.  public getData(index: number): string {
  8. 10.  return this.originDataArray[index];
  9. 11.  }
  10. 13.  registerDataChangeListener(listener: DataChangeListener): void {
  11. 14.  if (this.listeners.indexOf(listener) < 0) {
  12. 15.  console.info('add listener');
  13. 16.  this.listeners.push(listener);
  14. 17.  }
  15. 18.  }
  16. 20.  unregisterDataChangeListener(listener: DataChangeListener): void {
  17. 21.  const pos = this.listeners.indexOf(listener);
  18. 22.  if (pos >= 0) {
  19. 23.  console.info('remove listener');
  20. 24.  this.listeners.splice(pos, 1);
  21. 25.  }
  22. 26.  }
  23. 28.  notifyDataReload(): void {
  24. 29.  this.listeners.forEach(listener => {
  25. 30.  listener.onDataReloaded();
  26. 31.  })
  27. 32.  }
  28. 34.  notifyDataAdd(index: number): void {
  29. 35.  this.listeners.forEach(listener => {
  30. 36.  listener.onDataAdd(index);
  31. 37.  })
  32. 38.  }
  33. 40.  notifyDataChange(index: number): void {
  34. 41.  this.listeners.forEach(listener => {
  35. 42.  listener.onDataChange(index);
  36. 43.  })
  37. 44.  }
  38. 46.  notifyDataDelete(index: number): void {
  39. 47.  this.listeners.forEach(listener => {
  40. 48.  listener.onDataDelete(index);
  41. 49.  })
  42. 50.  }
  43. 51.  }
  44. 53.  class MyDataSource extends BasicDataSource {
  45. 54.  private dataArray: string[] = [];
  46. 56.  public totalCount(): number {
  47. 57.  return this.dataArray.length;
  48. 58.  }
  49. 60.  public getData(index: number): string {
  50. 61.  return this.dataArray[index];
  51. 62.  }
  52. 64.  public addData(index: number, data: string): void {
  53. 65.  this.dataArray.splice(index, 0, data);
  54. 66.  this.notifyDataAdd(index);
  55. 67.  }
  56. 69.  public pushData(data: string): void {
  57. 70.  this.dataArray.push(data);
  58. 71.  this.notifyDataAdd(this.dataArray.length - 1);
  59. 72.  }
  60. 74.  public deleteData(index: number): void {
  61. 75.  this.dataArray.splice(index, 1);
  62. 76.  this.notifyDataDelete(index);
  63. 77.  }
  64. 79.  public changeData(index: number): void {
  65. 80.  this.notifyDataChange(index);
  66. 81.  }
  67. 83.  public reloadData(): void {
  68. 84.  this.notifyDataReload();
  69. 85.  }
  70. 87.  public modifyAllData(): void {
  71. 88.  this.dataArray = this.dataArray.map((item: string) => {
  72. 89.  return item + '0';
  73. 90.  })
  74. 91.  }
  75. 92.  }
  76. 94.  @Entry
  77. 95.  @Component
  78. 96.  struct MyComponent {
  79. 97.  private moved: number[] = [];
  80. 98.  private data: MyDataSource = new MyDataSource();
  81. 100.  aboutToAppear() {
  82. 101.  for (let i = 0; i <= 20; i++) {
  83. 102.  this.data.pushData(`Hello ${i}`)
  84. 103.  }
  85. 104.  }
  86. 106.  build() {
  87. 107.  List({ space: 3 }) {
  88. 108.  LazyForEach(this.data, (item: string, index: number) => {
  89. 109.  ListItem() {
  90. 110.  Row() {
  91. 111.  Text(item).fontSize(50)
  92. 112.  .onAppear(() => {
  93. 113.  console.info("appear:" + item)
  94. 114.  })
  95. 115.  }.margin({ left: 10, right: 10 })
  96. 116.  }
  97. 117.  .onClick(() => {
  98. 118.  this.data.modifyAllData();
  99. 119.  this.data.reloadData();
  100. 120.  })
  101. 121.  }, (item: string) => item)
  102. 122.  }.cachedCount(5)
  103. 123.  }
  104. 124.  }
复制代码
当我们点击LazyForEach的子组件时,首先调用data的modifyAllData方法改变了数据源中的所有数据,然后调用数据源的reloadData方法,在该方法内会调用notifyDataReload方法。在notifyDataReload方法内会又调用listener.onDataReloaded方法,通知LazyForEach需要重建所有子节点。LazyForEach会将原所有数据项和新所有数据项一一做键值比对,如有相同键值则使用缓存,若键值不同则重新构建。
运行效果如下图所示。
图6 LazyForEach改变多个数据

若仅靠LazyForEach的革新机制,当item变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件布局较为复杂的情况下,靠改变键值去革新渲染性能较低。因此框架提供了@Observed与@ObjectLink机制举行深度观测,可以做到仅革新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种革新方式
  1. 1.  class BasicDataSource implements IDataSource {
  2. 2.  private listeners: DataChangeListener[] = [];
  3. 3.  private originDataArray: StringData[] = [];
  4. 5.  public totalCount(): number {
  5. 6.  return 0;
  6. 7.  }
  7. 9.  public getData(index: number): StringData {
  8. 10.  return this.originDataArray[index];
  9. 11.  }
  10. 13.  registerDataChangeListener(listener: DataChangeListener): void {
  11. 14.  if (this.listeners.indexOf(listener) < 0) {
  12. 15.  console.info('add listener');
  13. 16.  this.listeners.push(listener);
  14. 17.  }
  15. 18.  }
  16. 20.  unregisterDataChangeListener(listener: DataChangeListener): void {
  17. 21.  const pos = this.listeners.indexOf(listener);
  18. 22.  if (pos >= 0) {
  19. 23.  console.info('remove listener');
  20. 24.  this.listeners.splice(pos, 1);
  21. 25.  }
  22. 26.  }
  23. 28.  notifyDataReload(): void {
  24. 29.  this.listeners.forEach(listener => {
  25. 30.  listener.onDataReloaded();
  26. 31.  })
  27. 32.  }
  28. 34.  notifyDataAdd(index: number): void {
  29. 35.  this.listeners.forEach(listener => {
  30. 36.  listener.onDataAdd(index);
  31. 37.  })
  32. 38.  }
  33. 40.  notifyDataChange(index: number): void {
  34. 41.  this.listeners.forEach(listener => {
  35. 42.  listener.onDataChange(index);
  36. 43.  })
  37. 44.  }
  38. 46.  notifyDataDelete(index: number): void {
  39. 47.  this.listeners.forEach(listener => {
  40. 48.  listener.onDataDelete(index);
  41. 49.  })
  42. 50.  }
  43. 51.  }
  44. 53.  class MyDataSource extends BasicDataSource {
  45. 54.  private dataArray: StringData[] = [];
  46. 56.  public totalCount(): number {
  47. 57.  return this.dataArray.length;
  48. 58.  }
  49. 60.  public getData(index: number): StringData {
  50. 61.  return this.dataArray[index];
  51. 62.  }
  52. 64.  public addData(index: number, data: StringData): void {
  53. 65.  this.dataArray.splice(index, 0, data);
  54. 66.  this.notifyDataAdd(index);
  55. 67.  }
  56. 69.  public pushData(data: StringData): void {
  57. 70.  this.dataArray.push(data);
  58. 71.  this.notifyDataAdd(this.dataArray.length - 1);
  59. 72.  }
  60. 73.  }
  61. 75.  @Observed
  62. 76.  class StringData {
  63. 77.  message: string;
  64. 78.  constructor(message: string) {
  65. 79.  this.message = message;
  66. 80.  }
  67. 81.  }
  68. 83.  @Entry
  69. 84.  @Component
  70. 85.  struct MyComponent {
  71. 86.  private moved: number[] = [];
  72. 87.  @State data: MyDataSource = new MyDataSource();
  73. 89.  aboutToAppear() {
  74. 90.  for (let i = 0; i <= 20; i++) {
  75. 91.  this.data.pushData(new StringData(`Hello ${i}`));
  76. 92.  }
  77. 93.  }
  78. 95.  build() {
  79. 96.  List({ space: 3 }) {
  80. 97.  LazyForEach(this.data, (item: StringData, index: number) => {
  81. 98.  ListItem() {
  82. 99.  ChildComponent({data: item})
  83. 100.  }
  84. 101.  .onClick(() => {
  85. 102.  item.message += '0';
  86. 103.  })
  87. 104.  }, (item: StringData, index: number) => index.toString())
  88. 105.  }.cachedCount(5)
  89. 106.  }
  90. 107.  }
  91. 109.  @Component
  92. 110.  struct ChildComponent {
  93. 111.  @ObjectLink data: StringData
  94. 112.  build() {
  95. 113.  Row() {
  96. 114.  Text(this.data.message).fontSize(50)
  97. 115.  .onAppear(() => {
  98. 116.  console.info("appear:" + this.data.message)
  99. 117.  })
  100. 118.  }.margin({ left: 10, right: 10 })
  101. 119.  }
  102. 120.  }
复制代码
此时点击LazyForEach子组件改变item.message时,重渲染依赖的是ChildComponent的@ObjectLink成员变量对其子属性的监听,此时框架只会革新Text(this.data.message),不会去重建整个ListItem子组件。
图7 LazyForEach改变数据子属性

常见使用问题


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

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

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

@ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体请检察@ObjectLink与@Observed的具体使用方法和限定条件。
修复代码如下所示。
  1.     1.  class BasicDataSource implements IDataSource {
  2.     2.  private listeners: DataChangeListener[] = [];
  3.     3.  private originDataArray: StringData[] = [];
  4.    
  5.     5.  public totalCount(): number {
  6.     6.  return 0;
  7.     7.  }
  8.    
  9.     9.  public getData(index: number): StringData {
  10.     10.  return this.originDataArray[index];
  11.     11.  }
  12.    
  13.     13.  registerDataChangeListener(listener: DataChangeListener): void {
  14.     14.  if (this.listeners.indexOf(listener) < 0) {
  15.     15.  console.info('add listener');
  16.     16.  this.listeners.push(listener);
  17.     17.  }
  18.     18.  }
  19.    
  20.     20.  unregisterDataChangeListener(listener: DataChangeListener): void {
  21.     21.  const pos = this.listeners.indexOf(listener);
  22.     22.  if (pos >= 0) {
  23.     23.  console.info('remove listener');
  24.     24.  this.listeners.splice(pos, 1);
  25.     25.  }
  26.     26.  }
  27.    
  28.     28.  notifyDataReload(): void {
  29.     29.  this.listeners.forEach(listener => {
  30.     30.  listener.onDataReloaded();
  31.     31.  })
  32.     32.  }
  33.    
  34.     34.  notifyDataAdd(index: number): void {
  35.     35.  this.listeners.forEach(listener => {
  36.     36.  listener.onDataAdd(index);
  37.     37.  })
  38.     38.  }
  39.    
  40.     40.  notifyDataChange(index: number): void {
  41.     41.  this.listeners.forEach(listener => {
  42.     42.  listener.onDataChange(index);
  43.     43.  })
  44.     44.  }
  45.    
  46.     46.  notifyDataDelete(index: number): void {
  47.     47.  this.listeners.forEach(listener => {
  48.     48.  listener.onDataDelete(index);
  49.     49.  })
  50.     50.  }
  51.     51.  }
  52.    
  53.     53.  class MyDataSource extends BasicDataSource {
  54.     54.  private dataArray: StringData[] = [];
  55.    
  56.     56.  public totalCount(): number {
  57.     57.  return this.dataArray.length;
  58.     58.  }
  59.    
  60.     60.  public getData(index: number): StringData {
  61.     61.  return this.dataArray[index];
  62.     62.  }
  63.    
  64.     64.  public addData(index: number, data: StringData): void {
  65.     65.  this.dataArray.splice(index, 0, data);
  66.     66.  this.notifyDataAdd(index);
  67.     67.  }
  68.    
  69.     69.  public pushData(data: StringData): void {
  70.     70.  this.dataArray.push(data);
  71.     71.  this.notifyDataAdd(this.dataArray.length - 1);
  72.     72.  }
  73.     73.  }
  74.    
  75.     75.  @Observed
  76.     76.  class StringData {
  77.     77.  message: NestedString;
  78.     78.  constructor(message: NestedString) {
  79.     79.  this.message = message;
  80.     80.  }
  81.     81.  }
  82.    
  83.     83.  @Observed
  84.     84.  class NestedString {
  85.     85.  message: string;
  86.     86.  constructor(message: string) {
  87.     87.  this.message = message;
  88.     88.  }
  89.     89.  }
  90.    
  91.     91.  @Entry
  92.     92.  @Component
  93.     93.  struct MyComponent {
  94.     94.  private moved: number[] = [];
  95.     95.  @State data: MyDataSource = new MyDataSource();
  96.    
  97.     97.  aboutToAppear() {
  98.     98.  for (let i = 0; i <= 20; i++) {
  99.     99.  this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
  100.     100.  }
  101.     101.  }
  102.    
  103.     103.  build() {
  104.     104.  List({ space: 3 }) {
  105.     105.  LazyForEach(this.data, (item: StringData, index: number) => {
  106.     106.  ListItem() {
  107.     107.  ChildComponent({data: item})
  108.     108.  }
  109.     109.  .onClick(() => {
  110.     110.  item.message = new NestedString(item.message.message + '0');
  111.     111.  })
  112.     112.  }, (item: StringData, index: number) => item.toString() + index.toString())
  113.     113.  }.cachedCount(5)
  114.     114.  }
  115.     115.  }
  116.    
  117.     117.  @Component
  118.     118.  struct ChildComponent {
  119.     119.  @ObjectLink data: StringData
  120.     120.  build() {
  121.     121.  Row() {
  122.     122.  Text(this.data.message.message).fontSize(50)
  123.     123.  .onAppear(() => {
  124.     124.  console.info("appear:" + this.data.message.message)
  125.     125.  })
  126.     126.  }.margin({ left: 10, right: 10 })
  127.     127.  }
  128.     128.  }
复制代码
图13 修复ObjectLink属性变化后UI更新


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表