LazyForEach:數(shù)據(jù)懶加載

2024-01-25 12:09 更新

LazyForEach從提供的數(shù)據(jù)源中按需迭代數(shù)據(jù),并在每次迭代過程中創(chuàng)建相應(yīng)的組件。當(dāng)在滾動容器中使用了LazyForEach,框架會根據(jù)滾動容器可視區(qū)域按需創(chuàng)建組件,當(dāng)組件滑出可視區(qū)域外時,框架會進(jìn)行組件銷毀回收以降低內(nèi)存占用。

接口描述

  1. LazyForEach(
  2. dataSource: IDataSource, // 需要進(jìn)行數(shù)據(jù)迭代的數(shù)據(jù)源
  3. itemGenerator: (item: any, index?: number) => void, // 子組件生成函數(shù)
  4. keyGenerator?: (item: any, index?: number) => string // 鍵值生成函數(shù)
  5. ): void

參數(shù):

參數(shù)名

參數(shù)類型

必填

參數(shù)描述

dataSource

IDataSource

LazyForEach數(shù)據(jù)源,需要開發(fā)者實現(xiàn)相關(guān)接口。

itemGenerator

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

子組件生成函數(shù),為數(shù)組中的每一個數(shù)據(jù)項創(chuàng)建一個子組件。

說明:

item是當(dāng)前數(shù)據(jù)項,index是數(shù)據(jù)項索引值。

itemGenerator的函數(shù)體必須使用大括號{...}。itemGenerator每次迭代只能并且必須生成一個子組件。itemGenerator中可以使用if語句,但是必須保證if語句每個分支都會創(chuàng)建一個相同類型的子組件。itemGenerator中不允許使用ForEach和LazyForEach語句。

keyGenerator

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

鍵值生成函數(shù),用于給數(shù)據(jù)源中的每一個數(shù)據(jù)項生成唯一且固定的鍵值。當(dāng)數(shù)據(jù)項在數(shù)組中的位置更改時,其鍵值不得更改,當(dāng)數(shù)組中的數(shù)據(jù)項被新項替換時,被替換項的鍵值和新項的鍵值必須不同。鍵值生成器的功能是可選的,但是,為了使開發(fā)框架能夠更好地識別數(shù)組更改,提高性能,建議提供。如將數(shù)組反向時,如果沒有提供鍵值生成器,則LazyForEach中的所有節(jié)點(diǎn)都將重建。

說明:

item是當(dāng)前數(shù)據(jù)項,index是數(shù)據(jù)項索引值。

數(shù)據(jù)源中的每一個數(shù)據(jù)項生成的鍵值不能重復(fù)。

IDataSource類型說明

  1. interface IDataSource {
  2. totalCount(): number; // 獲得數(shù)據(jù)總數(shù)
  3. getData(index: number): Object; // 獲取索引值對應(yīng)的數(shù)據(jù)
  4. registerDataChangeListener(listener: DataChangeListener): void; // 注冊數(shù)據(jù)改變的監(jiān)聽器
  5. unregisterDataChangeListener(listener: DataChangeListener): void; // 注銷數(shù)據(jù)改變的監(jiān)聽器
  6. }

接口聲明

參數(shù)類型

說明

totalCount(): number

-

獲得數(shù)據(jù)總數(shù)。

getData(index: number): any

number

獲取索引值index對應(yīng)的數(shù)據(jù)。

index:獲取數(shù)據(jù)對應(yīng)的索引值。

registerDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注冊數(shù)據(jù)改變的監(jiān)聽器。

listener:數(shù)據(jù)變化監(jiān)聽器

unregisterDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注銷數(shù)據(jù)改變的監(jiān)聽器。

listener:數(shù)據(jù)變化監(jiān)聽器

DataChangeListener類型說明

  1. interface DataChangeListener {
  2. onDataReloaded(): void; // 重新加載數(shù)據(jù)完成后調(diào)用
  3. onDataAdded(index: number): void; // 添加數(shù)據(jù)完成后調(diào)用
  4. onDataMoved(from: number, to: number): void; // 數(shù)據(jù)移動起始位置與數(shù)據(jù)移動目標(biāo)位置交換完成后調(diào)用
  5. onDataDeleted(index: number): void; // 刪除數(shù)據(jù)完成后調(diào)用
  6. onDataChanged(index: number): void; // 改變數(shù)據(jù)完成后調(diào)用
  7. onDataAdd(index: number): void; // 添加數(shù)據(jù)完成后調(diào)用
  8. onDataMove(from: number, to: number): void; // 數(shù)據(jù)移動起始位置與數(shù)據(jù)移動目標(biāo)位置交換完成后調(diào)用
  9. onDataDelete(index: number): void; // 刪除數(shù)據(jù)完成后調(diào)用
  10. onDataChange(index: number): void; // 改變數(shù)據(jù)完成后調(diào)用
  11. }

接口聲明

參數(shù)類型

說明

onDataReloaded(): void

-

通知組件重新加載所有數(shù)據(jù)。

鍵值沒有變化的數(shù)據(jù)項會使用原先的子組件,鍵值發(fā)生變化的會重建子組件。

onDataAdd(index: number): void8+

number

通知組件index的位置有數(shù)據(jù)添加。

index:數(shù)據(jù)添加位置的索引值。

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

from: number,

to: number

通知組件數(shù)據(jù)有移動。

from: 數(shù)據(jù)移動起始位置,to: 數(shù)據(jù)移動目標(biāo)位置。

說明:

數(shù)據(jù)移動前后鍵值要保持不變,如果鍵值有變化,應(yīng)使用刪除數(shù)據(jù)和新增數(shù)據(jù)接口。

onDataDelete(index: number):void8+

number

通知組件刪除index位置的數(shù)據(jù)并刷新LazyForEach的展示內(nèi)容。

index:數(shù)據(jù)刪除位置的索引值。

說明:

需要保證dataSource中的對應(yīng)數(shù)據(jù)已經(jīng)在調(diào)用onDataDelete前刪除,否則頁面渲染將出現(xiàn)未定義的行為。

onDataChange(index: number): void8+

number

通知組件index的位置有數(shù)據(jù)有變化。

index:數(shù)據(jù)變化位置的索引值。

onDataAdded(index: number):void(deprecated)

number

通知組件index的位置有數(shù)據(jù)添加。

從API 8開始,建議使用onDataAdd。

index:數(shù)據(jù)添加位置的索引值。

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

from: number,

to: number

通知組件數(shù)據(jù)有移動。

從API 8開始,建議使用onDataMove。

from: 數(shù)據(jù)移動起始位置,to: 數(shù)據(jù)移動目標(biāo)位置。

將from和to位置的數(shù)據(jù)進(jìn)行交換。

說明:

數(shù)據(jù)移動前后鍵值要保持不變,如果鍵值有變化,應(yīng)使用刪除數(shù)據(jù)和新增數(shù)據(jù)接口。

onDataDeleted(index: number):void(deprecated)

number

通知組件刪除index位置的數(shù)據(jù)并刷新LazyForEach的展示內(nèi)容。

從API 8開始,建議使用onDataDelete。

index:數(shù)據(jù)刪除位置的索引值。

onDataChanged(index: number): void(deprecated)

number

通知組件index的位置有數(shù)據(jù)有變化。

從API 8開始,建議使用onDataChange。

index:數(shù)據(jù)變化監(jiān)聽器。

使用限制

  • LazyForEach必須在容器組件內(nèi)使用,僅有List、Grid、Swiper以及WaterFlow組件支持?jǐn)?shù)據(jù)懶加載(可配置cachedCount屬性,即只加載可視部分以及其前后少量數(shù)據(jù)用于緩沖),其他組件仍然是一次性加載所有的數(shù)據(jù)。
  • LazyForEach在每次迭代中,必須創(chuàng)建且只允許創(chuàng)建一個子組件。
  • 生成的子組件必須是允許包含在LazyForEach父容器組件中的子組件。
  • 允許LazyForEach包含在if/else條件渲染語句中,也允許LazyForEach中出現(xiàn)if/else條件渲染語句。
  • 鍵值生成器必須針對每個數(shù)據(jù)生成唯一的值,如果鍵值相同,將導(dǎo)致鍵值相同的UI組件被框架忽略,從而無法在父容器內(nèi)顯示。
  • LazyForEach必須使用DataChangeListener對象來進(jìn)行更新,第一個參數(shù)dataSource使用狀態(tài)變量時,狀態(tài)變量改變不會觸發(fā)LazyForEach的UI刷新。
  • 為了高性能渲染,通過DataChangeListener對象的onDataChange方法來更新UI時,需要生成不同于原來的鍵值來觸發(fā)組件刷新。

鍵值生成規(guī)則

在LazyForEach循環(huán)渲染過程中,系統(tǒng)會為每個item生成一個唯一且持久的鍵值,用于標(biāo)識對應(yīng)的組件。當(dāng)這個鍵值變化時,ArkUI框架將視為該數(shù)組元素已被替換或修改,并會基于新的鍵值創(chuàng)建一個新的組件。

LazyForEach提供了一個名為keyGenerator的參數(shù),這是一個函數(shù),開發(fā)者可以通過它自定義鍵值的生成規(guī)則。如果開發(fā)者沒有定義keyGenerator函數(shù),則ArkUI框架會使用默認(rèn)的鍵值生成函數(shù),即(item: any, index: number) => { return viewId + '-' + index.toString(); }, viewId在編譯器轉(zhuǎn)換過程中生成,同一個LazyForEach組件內(nèi)其viewId是一致的。

組件創(chuàng)建規(guī)則

在確定鍵值生成規(guī)則后,LazyForEach的第二個參數(shù)itemGenerator函數(shù)會根據(jù)鍵值生成規(guī)則為數(shù)據(jù)源的每個數(shù)組項創(chuàng)建組件。組件的創(chuàng)建包括兩種情況:LazyForEach首次渲染LazyForEach非首次渲染。

首次渲染

  • 生成不同鍵值

在LazyForEach首次渲染時,會根據(jù)上述鍵值生成規(guī)則為數(shù)據(jù)源的每個數(shù)組項生成唯一鍵值,并創(chuàng)建相應(yīng)的組件。

  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. // 該方法為框架側(cè)調(diào)用,為LazyForEach組件向其數(shù)據(jù)源處添加listener監(jiān)聽
  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. // 該方法為框架側(cè)調(diào)用,為對應(yīng)的LazyForEach組件在數(shù)據(jù)源處去除listener監(jiān)聽
  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對應(yīng)索引處添加子組件
  33. notifyDataAdd(index: number): void {
  34. this.listeners.forEach(listener => {
  35. listener.onDataAdd(index);
  36. })
  37. }
  38. // 通知LazyForEach組件在index對應(yīng)索引處數(shù)據(jù)有變化,需要重建該子組件
  39. notifyDataChange(index: number): void {
  40. this.listeners.forEach(listener => {
  41. listener.onDataChange(index);
  42. })
  43. }
  44. // 通知LazyForEach組件需要在index對應(yīng)索引處刪除該子組件
  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. }

在上述代碼中,鍵值生成規(guī)則是keyGenerator函數(shù)的返回值item。在LazyForEach循環(huán)渲染時,其為數(shù)據(jù)源數(shù)組項依次生成鍵值Hello 0、Hello 1 ... Hello 20,并創(chuàng)建對應(yīng)的ListItem子組件渲染到界面上。

運(yùn)行效果如下圖所示。

圖1 LazyForEach正常首次渲染

  • 鍵值相同時錯誤渲染

當(dāng)不同數(shù)據(jù)項生成的鍵值相同時,框架的行為是不可預(yù)測的。例如,在以下代碼中,LazyForEach渲染的數(shù)據(jù)項鍵值均相同,在滑動過程中,LazyForEach會對劃入劃出當(dāng)前頁面的子組件進(jìn)行預(yù)加載,而新建的子組件和銷毀的原子組件具有相同的鍵值,框架可能存在取用緩存錯誤的情況,導(dǎo)致子組件渲染有問題。

  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. }

運(yùn)行效果如下圖所示。可以看到Hello 0在滑動過程中被錯誤渲染為Hello 13。

圖2 LazyForEach存在相同鍵值

非首次渲染

當(dāng)LazyForEach數(shù)據(jù)源發(fā)生變化,需要再次渲染時,開發(fā)者應(yīng)根據(jù)數(shù)據(jù)源的變化情況調(diào)用listener對應(yīng)的接口,通知LazyForEach做相應(yīng)的更新,各使用場景如下。

  • 添加數(shù)據(jù)
  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. // 點(diǎn)擊追加子組件
  83. this.data.pushData(`Hello ${this.data.totalCount()}`);
  84. })
  85. }, (item: string) => item)
  86. }.cachedCount(5)
  87. }
  88. }

當(dāng)我們點(diǎn)擊LazyForEach的子組件時,首先調(diào)用數(shù)據(jù)源data的pushData方法,該方法會在數(shù)據(jù)源末尾添加數(shù)據(jù)并調(diào)用notifyDataAdd方法。在notifyDataAdd方法內(nèi)會又調(diào)用listener.onDataAdd方法,該方法會通知LazyForEach在該處有數(shù)據(jù)添加,LazyForEach便會在該索引處新建子組件。

運(yùn)行效果如下圖所示。

圖3 LazyForEach添加數(shù)據(jù)

  • 刪除數(shù)據(jù)
  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. // 點(diǎn)擊刪除子組件
  87. this.data.deleteData(this.data.dataArray.indexOf(item));
  88. })
  89. }, (item: string) => item)
  90. }.cachedCount(5)
  91. }
  92. }

當(dāng)我們點(diǎn)擊LazyForEach的子組件時,首先調(diào)用數(shù)據(jù)源data的deleteData方法,該方法會刪除數(shù)據(jù)源對應(yīng)索引處的數(shù)據(jù)并調(diào)用notifyDatDelete方法。在notifyDataDelete方法內(nèi)會又調(diào)用listener.onDataDelete方法,該方法會通知LazyForEach在該處有數(shù)據(jù)刪除,LazyForEach便會在該索引處刪除對應(yīng)子組件。

運(yùn)行效果如下圖所示。

圖4 LazyForEach刪除數(shù)據(jù)

  • 改變單個數(shù)據(jù)
  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. }

當(dāng)我們點(diǎn)擊LazyForEach的子組件時,首先改變當(dāng)前數(shù)據(jù),然后調(diào)用數(shù)據(jù)源data的changeData方法,在該方法內(nèi)會調(diào)用notifyDataChange方法。在notifyDataChange方法內(nèi)會又調(diào)用listener.onDataChange方法,該方法通知LazyForEach組件該處有數(shù)據(jù)發(fā)生變化,LazyForEach便會在對應(yīng)索引處重建子組件。

運(yùn)行效果如下圖所示。

圖5 LazyForEach改變單個數(shù)據(jù)

  • 改變多個數(shù)據(jù)
  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. }

當(dāng)我們點(diǎn)擊LazyForEach的子組件時,首先調(diào)用data的modifyAllData方法改變了數(shù)據(jù)源中的所有數(shù)據(jù),然后調(diào)用數(shù)據(jù)源的reloadData方法,在該方法內(nèi)會調(diào)用notifyDataReload方法。在notifyDataReload方法內(nèi)會又調(diào)用listener.onDataReloaded方法,通知LazyForEach需要重建所有子節(jié)點(diǎn)。LazyForEach會將原所有數(shù)據(jù)項和新所有數(shù)據(jù)項一一做鍵值比對,若有相同鍵值則使用緩存,若鍵值不同則重新構(gòu)建。

運(yùn)行效果如下圖所示。

圖6 LazyForEach改變多個數(shù)據(jù)

  • 改變數(shù)據(jù)子屬性

若僅靠LazyForEach的刷新機(jī)制,當(dāng)item變化時若想更新子組件,需要將原來的子組件全部銷毀再重新構(gòu)建,在子組件結(jié)構(gòu)較為復(fù)雜的情況下,靠改變鍵值去刷新渲染性能較低。因此框架提供了@Observed與@ObjectLink機(jī)制進(jìn)行深度觀測,可以做到僅刷新使用了該屬性的組件,提高渲染性能。開發(fā)者可根據(jù)其自身業(yè)務(wù)特點(diǎn)選擇使用哪種刷新方式。

  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. }

此時點(diǎn)擊LazyForEach子組件改變item.message時,重渲染依賴的是ChildComponent的@ObjectLink成員變量對其子屬性的監(jiān)聽,此時框架只會刷新Text(this.data.message),不會去重建整個ListItem子組件。

圖7 LazyForEach改變數(shù)據(jù)子屬性

常見使用問題

  • 渲染結(jié)果非預(yù)期
    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. // 點(diǎn)擊刪除子組件
    87. this.data.deleteData(index);
    88. })
    89. }, (item: string) => item)
    90. }.cachedCount(5)
    91. }
    92. }

    圖8 LazyForEach刪除數(shù)據(jù)非預(yù)期

    當(dāng)我們多次點(diǎn)擊子組件時,會發(fā)現(xiàn)刪除的并不一定是我們點(diǎn)擊的那個子組件。原因是當(dāng)我們刪除了某一個子組件后,位于該子組件對應(yīng)的數(shù)據(jù)項之后的各數(shù)據(jù)項,其index均應(yīng)減1,但實際上后續(xù)的數(shù)據(jù)項對應(yīng)的子組件仍然使用的是最初分配的index,其itemGenerator中的index并沒有發(fā)生變化,所以刪除結(jié)果和預(yù)期不符。

    修復(fù)代碼如下所示。

    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. // 點(diǎn)擊刪除子組件
    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. }

    在刪除一個數(shù)據(jù)項后調(diào)用reloadData方法,重建后面的數(shù)據(jù)項,以達(dá)到更新index索引的目的。

    圖9 修復(fù)LazyForEach刪除數(shù)據(jù)非預(yù)期

  • 重渲染時圖片閃爍
    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僅改變文字但是圖片閃爍問題

    在我們點(diǎn)擊ListItem子組件時,我們只改變了數(shù)據(jù)項的message屬性,但是LazyForEach的刷新機(jī)制會導(dǎo)致整個ListItem被重建。由于Image組件是異步刷新,所以視覺上圖片會發(fā)生閃爍。為了解決這種情況我們應(yīng)該使用@ObjectLink和@Observed去單獨(dú)刷新使用了item.message的Text組件。

    修復(fù)代碼如下所示。

    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 修復(fù)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裝飾的成員變量僅能監(jiān)聽到其子屬性的變化,再深入嵌套的屬性便無法觀測到了,因此我們只能改變它的子屬性去通知對應(yīng)組件重新渲染,具體請查看@ObjectLink與@Observed的詳細(xì)使用方法和限制條件。

    修復(fù)代碼如下所示。

    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 修復(fù)ObjectLink屬性變化后UI更新

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號