codecamp

LazyForEach:数据懒加载

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

接口描述

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

参数:

参数名

参数类型

必填

参数描述

dataSource

IDataSource

LazyForEach数据源,需要开发者实现相关接口。

itemGenerator

(item: any, index?:number) => void

子组件生成函数,为数组中的每一个数据项创建一个子组件。

说明:

item是当前数据项,index是数据项索引值。

itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须生成一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不允许使用ForEach和LazyForEach语句。

keyGenerator

(item: any, index?:number) => string

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

说明:

item是当前数据项,index是数据项索引值。

数据源中的每一个数据项生成的键值不能重复。

IDataSource类型说明

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

接口声明

参数类型

说明

totalCount(): number

-

获得数据总数。

getData(index: number): any

number

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

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

registerDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注册数据改变的监听器。

listener:数据变化监听器

unregisterDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注销数据改变的监听器。

listener:数据变化监听器

DataChangeListener类型说明

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

接口声明

参数类型

说明

onDataReloaded(): void

-

通知组件重新加载所有数据。

键值没有变化的数据项会使用原先的子组件,键值发生变化的会重建子组件。

onDataAdd(index: number): void8+

number

通知组件index的位置有数据添加。

index:数据添加位置的索引值。

onDataMove(from: number, to: number): void8+

from: number,

to: number

通知组件数据有移动。

from: 数据移动起始位置,to: 数据移动目标位置。

说明:

数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。

onDataDelete(index: number):void8+

number

通知组件删除index位置的数据并刷新LazyForEach的展示内容。

index:数据删除位置的索引值。

说明:

需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。

onDataChange(index: number): void8+

number

通知组件index的位置有数据有变化。

index:数据变化位置的索引值。

onDataAdded(index: number):void(deprecated)

number

通知组件index的位置有数据添加。

从API 8开始,建议使用onDataAdd。

index:数据添加位置的索引值。

onDataMoved(from: number, to: number): void(deprecated)

from: number,

to: number

通知组件数据有移动。

从API 8开始,建议使用onDataMove。

from: 数据移动起始位置,to: 数据移动目标位置。

将from和to位置的数据进行交换。

说明:

数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。

onDataDeleted(index: number):void(deprecated)

number

通知组件删除index位置的数据并刷新LazyForEach的展示内容。

从API 8开始,建议使用onDataDelete。

index:数据删除位置的索引值。

onDataChanged(index: number): void(deprecated)

number

通知组件index的位置有数据有变化。

从API 8开始,建议使用onDataChange。

index:数据变化监听器。

使用限制

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

键值生成规则

在LazyForEach循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。

LazyForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: any, index: number) => { return viewId + '-' + index.toString(); }, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。

组件创建规则

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

首次渲染

  • 生成不同键值

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

  1. // Basic implementation of IDataSource to handle data listener
  2. class BasicDataSource implements IDataSource {
  3. private listeners: DataChangeListener[] = [];
  4. private originDataArray: string[] = [];
  5. public totalCount(): number {
  6. return 0;
  7. }
  8. public getData(index: number): string {
  9. return this.originDataArray[index];
  10. }
  11. // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
  12. registerDataChangeListener(listener: DataChangeListener): void {
  13. if (this.listeners.indexOf(listener) < 0) {
  14. console.info('add listener');
  15. this.listeners.push(listener);
  16. }
  17. }
  18. // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
  19. unregisterDataChangeListener(listener: DataChangeListener): void {
  20. const pos = this.listeners.indexOf(listener);
  21. if (pos >= 0) {
  22. console.info('remove listener');
  23. this.listeners.splice(pos, 1);
  24. }
  25. }
  26. // 通知LazyForEach组件需要重载所有子组件
  27. notifyDataReload(): void {
  28. this.listeners.forEach(listener => {
  29. listener.onDataReloaded();
  30. })
  31. }
  32. // 通知LazyForEach组件需要在index对应索引处添加子组件
  33. notifyDataAdd(index: number): void {
  34. this.listeners.forEach(listener => {
  35. listener.onDataAdd(index);
  36. })
  37. }
  38. // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
  39. notifyDataChange(index: number): void {
  40. this.listeners.forEach(listener => {
  41. listener.onDataChange(index);
  42. })
  43. }
  44. // 通知LazyForEach组件需要在index对应索引处删除该子组件
  45. notifyDataDelete(index: number): void {
  46. this.listeners.forEach(listener => {
  47. listener.onDataDelete(index);
  48. })
  49. }
  50. }
  51. class MyDataSource extends BasicDataSource {
  52. private dataArray: string[] = [];
  53. public totalCount(): number {
  54. return this.dataArray.length;
  55. }
  56. public getData(index: number): string {
  57. return this.dataArray[index];
  58. }
  59. public addData(index: number, data: string): void {
  60. this.dataArray.splice(index, 0, data);
  61. this.notifyDataAdd(index);
  62. }
  63. public pushData(data: string): void {
  64. this.dataArray.push(data);
  65. this.notifyDataAdd(this.dataArray.length - 1);
  66. }
  67. }
  68. @Entry
  69. @Component
  70. struct MyComponent {
  71. private data: MyDataSource = new MyDataSource();
  72. aboutToAppear() {
  73. for (let i = 0; i <= 20; i++) {
  74. this.data.pushData(`Hello ${i}`)
  75. }
  76. }
  77. build() {
  78. List({ space: 3 }) {
  79. LazyForEach(this.data, (item: string) => {
  80. ListItem() {
  81. Row() {
  82. Text(item).fontSize(50)
  83. .onAppear(() => {
  84. console.info("appear:" + item)
  85. })
  86. }.margin({ left: 10, right: 10 })
  87. }
  88. }, (item: string) => item)
  89. }.cachedCount(5)
  90. }
  91. }

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

运行效果如下图所示。

图1 LazyForEach正常首次渲染

  • 键值相同时错误渲染

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

  1. class BasicDataSource implements IDataSource {
  2. private listeners: DataChangeListener[] = [];
  3. private originDataArray: string[] = [];
  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. }
  44. class MyDataSource extends BasicDataSource {
  45. private dataArray: string[] = [];
  46. public totalCount(): number {
  47. return this.dataArray.length;
  48. }
  49. public getData(index: number): string {
  50. return this.dataArray[index];
  51. }
  52. public addData(index: number, data: string): void {
  53. this.dataArray.splice(index, 0, data);
  54. this.notifyDataAdd(index);
  55. }
  56. public pushData(data: string): void {
  57. this.dataArray.push(data);
  58. this.notifyDataAdd(this.dataArray.length - 1);
  59. }
  60. }
  61. @Entry
  62. @Component
  63. struct MyComponent {
  64. private data: MyDataSource = new MyDataSource();
  65. aboutToAppear() {
  66. for (let i = 0; i <= 20; i++) {
  67. this.data.pushData(`Hello ${i}`)
  68. }
  69. }
  70. build() {
  71. List({ space: 3 }) {
  72. LazyForEach(this.data, (item: string) => {
  73. ListItem() {
  74. Row() {
  75. Text(item).fontSize(50)
  76. .onAppear(() => {
  77. console.info("appear:" + item)
  78. })
  79. }.margin({ left: 10, right: 10 })
  80. }
  81. }, (item: string) => 'same key')
  82. }.cachedCount(5)
  83. }
  84. }

运行效果如下图所示。可以看到Hello 0在滑动过程中被错误渲染为Hello 13。

图2 LazyForEach存在相同键值

非首次渲染

当LazyForEach数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用listener对应的接口,通知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. }
  44. class MyDataSource extends BasicDataSource {
  45. private dataArray: string[] = [];
  46. public totalCount(): number {
  47. return this.dataArray.length;
  48. }
  49. public getData(index: number): string {
  50. return this.dataArray[index];
  51. }
  52. public addData(index: number, data: string): void {
  53. this.dataArray.splice(index, 0, data);
  54. this.notifyDataAdd(index);
  55. }
  56. public pushData(data: string): void {
  57. this.dataArray.push(data);
  58. this.notifyDataAdd(this.dataArray.length - 1);
  59. }
  60. }
  61. @Entry
  62. @Component
  63. struct MyComponent {
  64. private data: MyDataSource = new MyDataSource();
  65. aboutToAppear() {
  66. for (let i = 0; i <= 20; i++) {
  67. this.data.pushData(`Hello ${i}`)
  68. }
  69. }
  70. build() {
  71. List({ space: 3 }) {
  72. LazyForEach(this.data, (item: string) => {
  73. ListItem() {
  74. Row() {
  75. Text(item).fontSize(50)
  76. .onAppear(() => {
  77. console.info("appear:" + item)
  78. })
  79. }.margin({ left: 10, right: 10 })
  80. }
  81. .onClick(() => {
  82. // 点击追加子组件
  83. this.data.pushData(`Hello ${this.data.totalCount()}`);
  84. })
  85. }, (item: string) => item)
  86. }.cachedCount(5)
  87. }
  88. }

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

运行效果如下图所示。

图3 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. }
  44. class MyDataSource extends BasicDataSource {
  45. dataArray: string[] = [];
  46. public totalCount(): number {
  47. return this.dataArray.length;
  48. }
  49. public getData(index: number): string {
  50. return this.dataArray[index];
  51. }
  52. public addData(index: number, data: string): void {
  53. this.dataArray.splice(index, 0, data);
  54. this.notifyDataAdd(index);
  55. }
  56. public pushData(data: string): void {
  57. this.dataArray.push(data);
  58. this.notifyDataAdd(this.dataArray.length - 1);
  59. }
  60. public deleteData(index: number): void {
  61. this.dataArray.splice(index, 1);
  62. this.notifyDataDelete(index);
  63. }
  64. }
  65. @Entry
  66. @Component
  67. struct MyComponent {
  68. private data: MyDataSource = new MyDataSource();
  69. aboutToAppear() {
  70. for (let i = 0; i <= 20; i++) {
  71. this.data.pushData(`Hello ${i}`)
  72. }
  73. }
  74. build() {
  75. List({ space: 3 }) {
  76. LazyForEach(this.data, (item: string, index: number) => {
  77. ListItem() {
  78. Row() {
  79. Text(item).fontSize(50)
  80. .onAppear(() => {
  81. console.info("appear:" + item)
  82. })
  83. }.margin({ left: 10, right: 10 })
  84. }
  85. .onClick(() => {
  86. // 点击删除子组件
  87. this.data.deleteData(this.data.dataArray.indexOf(item));
  88. })
  89. }, (item: string) => item)
  90. }.cachedCount(5)
  91. }
  92. }

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

运行效果如下图所示。

图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. }
  44. class MyDataSource extends BasicDataSource {
  45. private dataArray: string[] = [];
  46. public totalCount(): number {
  47. return this.dataArray.length;
  48. }
  49. public getData(index: number): string {
  50. return this.dataArray[index];
  51. }
  52. public addData(index: number, data: string): void {
  53. this.dataArray.splice(index, 0, data);
  54. this.notifyDataAdd(index);
  55. }
  56. public pushData(data: string): void {
  57. this.dataArray.push(data);
  58. this.notifyDataAdd(this.dataArray.length - 1);
  59. }
  60. public deleteData(index: number): void {
  61. this.dataArray.splice(index, 1);
  62. this.notifyDataDelete(index);
  63. }
  64. public changeData(index: number, data: string): void {
  65. this.dataArray.splice(index, 1, data);
  66. this.notifyDataChange(index);
  67. }
  68. }
  69. @Entry
  70. @Component
  71. struct MyComponent {
  72. private moved: number[] = [];
  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. this.data.changeData(index, item + '00');
  92. })
  93. }, (item: string) => item)
  94. }.cachedCount(5)
  95. }
  96. }

当我们点击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. }
  44. class MyDataSource extends BasicDataSource {
  45. private dataArray: string[] = [];
  46. public totalCount(): number {
  47. return this.dataArray.length;
  48. }
  49. public getData(index: number): string {
  50. return this.dataArray[index];
  51. }
  52. public addData(index: number, data: string): void {
  53. this.dataArray.splice(index, 0, data);
  54. this.notifyDataAdd(index);
  55. }
  56. public pushData(data: string): void {
  57. this.dataArray.push(data);
  58. this.notifyDataAdd(this.dataArray.length - 1);
  59. }
  60. public deleteData(index: number): void {
  61. this.dataArray.splice(index, 1);
  62. this.notifyDataDelete(index);
  63. }
  64. public changeData(index: number): void {
  65. this.notifyDataChange(index);
  66. }
  67. public reloadData(): void {
  68. this.notifyDataReload();
  69. }
  70. public modifyAllData(): void {
  71. this.dataArray = this.dataArray.map((item: string) => {
  72. return item + '0';
  73. })
  74. }
  75. }
  76. @Entry
  77. @Component
  78. struct MyComponent {
  79. private moved: number[] = [];
  80. private data: MyDataSource = new MyDataSource();
  81. aboutToAppear() {
  82. for (let i = 0; i <= 20; i++) {
  83. this.data.pushData(`Hello ${i}`)
  84. }
  85. }
  86. build() {
  87. List({ space: 3 }) {
  88. LazyForEach(this.data, (item: string, index: number) => {
  89. ListItem() {
  90. Row() {
  91. Text(item).fontSize(50)
  92. .onAppear(() => {
  93. console.info("appear:" + item)
  94. })
  95. }.margin({ left: 10, right: 10 })
  96. }
  97. .onClick(() => {
  98. this.data.modifyAllData();
  99. this.data.reloadData();
  100. })
  101. }, (item: string) => item)
  102. }.cachedCount(5)
  103. }
  104. }

当我们点击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. }
  44. class MyDataSource extends BasicDataSource {
  45. private dataArray: StringData[] = [];
  46. public totalCount(): number {
  47. return this.dataArray.length;
  48. }
  49. public getData(index: number): StringData {
  50. return this.dataArray[index];
  51. }
  52. public addData(index: number, data: StringData): void {
  53. this.dataArray.splice(index, 0, data);
  54. this.notifyDataAdd(index);
  55. }
  56. public pushData(data: StringData): void {
  57. this.dataArray.push(data);
  58. this.notifyDataAdd(this.dataArray.length - 1);
  59. }
  60. }
  61. @Observed
  62. class StringData {
  63. message: string;
  64. constructor(message: string) {
  65. this.message = message;
  66. }
  67. }
  68. @Entry
  69. @Component
  70. struct MyComponent {
  71. private moved: number[] = [];
  72. @State data: MyDataSource = new MyDataSource();
  73. aboutToAppear() {
  74. for (let i = 0; i <= 20; i++) {
  75. this.data.pushData(new StringData(`Hello ${i}`));
  76. }
  77. }
  78. build() {
  79. List({ space: 3 }) {
  80. LazyForEach(this.data, (item: StringData, index: number) => {
  81. ListItem() {
  82. ChildComponent({data: item})
  83. }
  84. .onClick(() => {
  85. item.message += '0';
  86. })
  87. }, (item: StringData, index: number) => index.toString())
  88. }.cachedCount(5)
  89. }
  90. }
  91. @Component
  92. struct ChildComponent {
  93. @ObjectLink data: StringData
  94. build() {
  95. Row() {
  96. Text(this.data.message).fontSize(50)
  97. .onAppear(() => {
  98. console.info("appear:" + this.data.message)
  99. })
  100. }.margin({ left: 10, right: 10 })
  101. }
  102. }

此时点击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. }
    44. class MyDataSource extends BasicDataSource {
    45. private dataArray: string[] = [];
    46. public totalCount(): number {
    47. return this.dataArray.length;
    48. }
    49. public getData(index: number): string {
    50. return this.dataArray[index];
    51. }
    52. public addData(index: number, data: string): void {
    53. this.dataArray.splice(index, 0, data);
    54. this.notifyDataAdd(index);
    55. }
    56. public pushData(data: string): void {
    57. this.dataArray.push(data);
    58. this.notifyDataAdd(this.dataArray.length - 1);
    59. }
    60. public deleteData(index: number): void {
    61. this.dataArray.splice(index, 1);
    62. this.notifyDataDelete(index);
    63. }
    64. }
    65. @Entry
    66. @Component
    67. struct MyComponent {
    68. private data: MyDataSource = new MyDataSource();
    69. aboutToAppear() {
    70. for (let i = 0; i <= 20; i++) {
    71. this.data.pushData(`Hello ${i}`)
    72. }
    73. }
    74. build() {
    75. List({ space: 3 }) {
    76. LazyForEach(this.data, (item: string, index: number) => {
    77. ListItem() {
    78. Row() {
    79. Text(item).fontSize(50)
    80. .onAppear(() => {
    81. console.info("appear:" + item)
    82. })
    83. }.margin({ left: 10, right: 10 })
    84. }
    85. .onClick(() => {
    86. // 点击删除子组件
    87. this.data.deleteData(index);
    88. })
    89. }, (item: string) => item)
    90. }.cachedCount(5)
    91. }
    92. }

    图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. }
    44. class MyDataSource extends BasicDataSource {
    45. private dataArray: string[] = [];
    46. public totalCount(): number {
    47. return this.dataArray.length;
    48. }
    49. public getData(index: number): string {
    50. return this.dataArray[index];
    51. }
    52. public addData(index: number, data: string): void {
    53. this.dataArray.splice(index, 0, data);
    54. this.notifyDataAdd(index);
    55. }
    56. public pushData(data: string): void {
    57. this.dataArray.push(data);
    58. this.notifyDataAdd(this.dataArray.length - 1);
    59. }
    60. public deleteData(index: number): void {
    61. this.dataArray.splice(index, 1);
    62. this.notifyDataDelete(index);
    63. }
    64. public reloadData(): void {
    65. this.notifyDataReload();
    66. }
    67. }
    68. @Entry
    69. @Component
    70. struct MyComponent {
    71. private data: MyDataSource = new MyDataSource();
    72. aboutToAppear() {
    73. for (let i = 0; i <= 20; i++) {
    74. this.data.pushData(`Hello ${i}`)
    75. }
    76. }
    77. build() {
    78. List({ space: 3 }) {
    79. LazyForEach(this.data, (item: string, index: number) => {
    80. ListItem() {
    81. Row() {
    82. Text(item).fontSize(50)
    83. .onAppear(() => {
    84. console.info("appear:" + item)
    85. })
    86. }.margin({ left: 10, right: 10 })
    87. }
    88. .onClick(() => {
    89. // 点击删除子组件
    90. this.data.deleteData(index);
    91. // 重置所有子组件的index索引
    92. this.data.reloadData();
    93. })
    94. }, (item: string, index: number) => item + index.toString())
    95. }.cachedCount(5)
    96. }
    97. }

    在删除一个数据项后调用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. }
    44. class MyDataSource extends BasicDataSource {
    45. private dataArray: StringData[] = [];
    46. public totalCount(): number {
    47. return this.dataArray.length;
    48. }
    49. public getData(index: number): StringData {
    50. return this.dataArray[index];
    51. }
    52. public addData(index: number, data: StringData): void {
    53. this.dataArray.splice(index, 0, data);
    54. this.notifyDataAdd(index);
    55. }
    56. public pushData(data: StringData): void {
    57. this.dataArray.push(data);
    58. this.notifyDataAdd(this.dataArray.length - 1);
    59. }
    60. public reloadData(): void {
    61. this.notifyDataReload();
    62. }
    63. }
    64. class StringData {
    65. message: string;
    66. imgSrc: Resource;
    67. constructor(message: string, imgSrc: Resource) {
    68. this.message = message;
    69. this.imgSrc = imgSrc;
    70. }
    71. }
    72. @Entry
    73. @Component
    74. struct MyComponent {
    75. private moved: number[] = [];
    76. private data: MyDataSource = new MyDataSource();
    77. aboutToAppear() {
    78. for (let i = 0; i <= 20; i++) {
    79. this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
    80. }
    81. }
    82. build() {
    83. List({ space: 3 }) {
    84. LazyForEach(this.data, (item: StringData, index: number) => {
    85. ListItem() {
    86. Column() {
    87. Text(item.message).fontSize(50)
    88. .onAppear(() => {
    89. console.info("appear:" + item.message)
    90. })
    91. Image(item.imgSrc)
    92. .width(500)
    93. .height(200)
    94. }.margin({ left: 10, right: 10 })
    95. }
    96. .onClick(() => {
    97. item.message += '00';
    98. this.data.reloadData();
    99. })
    100. }, (item: StringData, index: number) => JSON.stringify(item))
    101. }.cachedCount(5)
    102. }
    103. }

    图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. }
    44. class MyDataSource extends BasicDataSource {
    45. private dataArray: StringData[] = [];
    46. public totalCount(): number {
    47. return this.dataArray.length;
    48. }
    49. public getData(index: number): StringData {
    50. return this.dataArray[index];
    51. }
    52. public addData(index: number, data: StringData): void {
    53. this.dataArray.splice(index, 0, data);
    54. this.notifyDataAdd(index);
    55. }
    56. public pushData(data: StringData): void {
    57. this.dataArray.push(data);
    58. this.notifyDataAdd(this.dataArray.length - 1);
    59. }
    60. }
    61. @Observed
    62. class StringData {
    63. message: string;
    64. imgSrc: Resource;
    65. constructor(message: string, imgSrc: Resource) {
    66. this.message = message;
    67. this.imgSrc = imgSrc;
    68. }
    69. }
    70. @Entry
    71. @Component
    72. struct MyComponent {
    73. @State data: MyDataSource = new MyDataSource();
    74. aboutToAppear() {
    75. for (let i = 0; i <= 20; i++) {
    76. this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img')));
    77. }
    78. }
    79. build() {
    80. List({ space: 3 }) {
    81. LazyForEach(this.data, (item: StringData, index: number) => {
    82. ListItem() {
    83. ChildComponent({data: item})
    84. }
    85. .onClick(() => {
    86. item.message += '0';
    87. })
    88. }, (item: StringData, index: number) => index.toString())
    89. }.cachedCount(5)
    90. }
    91. }
    92. @Component
    93. struct ChildComponent {
    94. @ObjectLink data: StringData
    95. build() {
    96. Column() {
    97. Text(this.data.message).fontSize(50)
    98. .onAppear(() => {
    99. console.info("appear:" + this.data.message)
    100. })
    101. Image(this.data.imgSrc)
    102. .width(500)
    103. .height(200)
    104. }.margin({ left: 10, right: 10 })
    105. }
    106. }

    图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. }
    44. class MyDataSource extends BasicDataSource {
    45. private dataArray: StringData[] = [];
    46. public totalCount(): number {
    47. return this.dataArray.length;
    48. }
    49. public getData(index: number): StringData {
    50. return this.dataArray[index];
    51. }
    52. public addData(index: number, data: StringData): void {
    53. this.dataArray.splice(index, 0, data);
    54. this.notifyDataAdd(index);
    55. }
    56. public pushData(data: StringData): void {
    57. this.dataArray.push(data);
    58. this.notifyDataAdd(this.dataArray.length - 1);
    59. }
    60. }
    61. @Observed
    62. class StringData {
    63. message: NestedString;
    64. constructor(message: NestedString) {
    65. this.message = message;
    66. }
    67. }
    68. @Observed
    69. class NestedString {
    70. message: string;
    71. constructor(message: string) {
    72. this.message = message;
    73. }
    74. }
    75. @Entry
    76. @Component
    77. struct MyComponent {
    78. private moved: number[] = [];
    79. @State data: MyDataSource = new MyDataSource();
    80. aboutToAppear() {
    81. for (let i = 0; i <= 20; i++) {
    82. this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
    83. }
    84. }
    85. build() {
    86. List({ space: 3 }) {
    87. LazyForEach(this.data, (item: StringData, index: number) => {
    88. ListItem() {
    89. ChildComponent({data: item})
    90. }
    91. .onClick(() => {
    92. item.message.message += '0';
    93. })
    94. }, (item: StringData, index: number) => item.toString() + index.toString())
    95. }.cachedCount(5)
    96. }
    97. }
    98. @Component
    99. struct ChildComponent {
    100. @ObjectLink data: StringData
    101. build() {
    102. Row() {
    103. Text(this.data.message.message).fontSize(50)
    104. .onAppear(() => {
    105. console.info("appear:" + this.data.message.message)
    106. })
    107. }.margin({ left: 10, right: 10 })
    108. }
    109. }

    图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. }
    44. class MyDataSource extends BasicDataSource {
    45. private dataArray: StringData[] = [];
    46. public totalCount(): number {
    47. return this.dataArray.length;
    48. }
    49. public getData(index: number): StringData {
    50. return this.dataArray[index];
    51. }
    52. public addData(index: number, data: StringData): void {
    53. this.dataArray.splice(index, 0, data);
    54. this.notifyDataAdd(index);
    55. }
    56. public pushData(data: StringData): void {
    57. this.dataArray.push(data);
    58. this.notifyDataAdd(this.dataArray.length - 1);
    59. }
    60. }
    61. @Observed
    62. class StringData {
    63. message: NestedString;
    64. constructor(message: NestedString) {
    65. this.message = message;
    66. }
    67. }
    68. @Observed
    69. class NestedString {
    70. message: string;
    71. constructor(message: string) {
    72. this.message = message;
    73. }
    74. }
    75. @Entry
    76. @Component
    77. struct MyComponent {
    78. private moved: number[] = [];
    79. @State data: MyDataSource = new MyDataSource();
    80. aboutToAppear() {
    81. for (let i = 0; i <= 20; i++) {
    82. this.data.pushData(new StringData(new NestedString(`Hello ${i}`)));
    83. }
    84. }
    85. build() {
    86. List({ space: 3 }) {
    87. LazyForEach(this.data, (item: StringData, index: number) => {
    88. ListItem() {
    89. ChildComponent({data: item})
    90. }
    91. .onClick(() => {
    92. item.message = new NestedString(item.message.message + '0');
    93. })
    94. }, (item: StringData, index: number) => item.toString() + index.toString())
    95. }.cachedCount(5)
    96. }
    97. }
    98. @Component
    99. struct ChildComponent {
    100. @ObjectLink data: StringData
    101. build() {
    102. Row() {
    103. Text(this.data.message.message).fontSize(50)
    104. .onAppear(() => {
    105. console.info("appear:" + this.data.message.message)
    106. })
    107. }.margin({ left: 10, right: 10 })
    108. }
    109. }

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

ForEach:循环渲染
应用模型的构成要素
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录
HAR

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }