ForEach:循環(huán)渲染

2024-01-25 12:08 更新

ForEach接口基于數(shù)組類型數(shù)據(jù)來進行循環(huán)渲染,需要與容器組件配合使用,且接口返回的組件應當是允許包含在ForEach父容器組件中的子組件。例如,ListItem組件要求ForEach的父容器組件必須為List組件。

說明

從API version 9開始,該接口支持在ArkTS卡片中使用。

接口描述

  1. ForEach(
  2. arr: Array,
  3. itemGenerator: (item: any, index?: number) => void,
  4. keyGenerator?: (item: any, index?: number) => string
  5. )

參數(shù)名

參數(shù)類型

必填

參數(shù)描述

arr

Array

數(shù)據(jù)源,為Array類型的數(shù)組。

說明

- 可以設置為空數(shù)組,此時不會創(chuàng)建子組件。

- 可以設置返回值為數(shù)組類型的函數(shù),例如arr.slice(1, 3),但設置的函數(shù)不應改變包括數(shù)組本身在內(nèi)的任何狀態(tài)變量,例如不應使用Array.splice(),Array.sort()或Array.reverse()這些會改變原數(shù)組的函數(shù)。

itemGenerator

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

組件生成函數(shù)。

- 為數(shù)組中的每個元素創(chuàng)建對應的組件。

- item參數(shù):arr數(shù)組中的數(shù)據(jù)項。

- index參數(shù)(可選):arr數(shù)組中的數(shù)據(jù)項索引。

說明

- 組件的類型必須是ForEach的父容器所允許的。例如,ListItem組件要求ForEach的父容器組件必須為List組件。

keyGenerator

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

鍵值生成函數(shù)。

- 為數(shù)據(jù)源arr的每個數(shù)組項生成唯一且持久的鍵值。函數(shù)返回值為開發(fā)者自定義的鍵值生成規(guī)則。

- item參數(shù):arr數(shù)組中的數(shù)據(jù)項。- index參數(shù)(可選):arr數(shù)組中的數(shù)據(jù)項索引。

說明

- 如果函數(shù)缺省,框架默認的鍵值生成函數(shù)為(item: T, index: number) => { return index + '__' + JSON.stringify(item); }

- 鍵值生成函數(shù)不應改變?nèi)魏谓M件狀態(tài)。

說明
  • ForEach的itemGenerator函數(shù)可以包含if/else條件渲染邏輯。另外,也可以在if/else條件渲染語句中使用ForEach組件。
  • 在初始化渲染時,F(xiàn)orEach會加載數(shù)據(jù)源的所有數(shù)據(jù),并為每個數(shù)據(jù)項創(chuàng)建對應的組件,然后將其掛載到渲染樹上。如果數(shù)據(jù)源非常大或有特定的性能需求,建議使用LazyForEach組件。

鍵值生成規(guī)則

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

ForEach提供了一個名為keyGenerator的參數(shù),這是一個函數(shù),開發(fā)者可以通過它自定義鍵值的生成規(guī)則。如果開發(fā)者沒有定義keyGenerator函數(shù),則ArkUI框架會使用默認的鍵值生成函數(shù),即(item: any, index: number) => { return index + '__' + JSON.stringify(item); }。

ArkUI框架對于ForEach的鍵值生成有一套特定的判斷規(guī)則,這主要與itemGenerator函數(shù)的第二個參數(shù)index以及keyGenerator函數(shù)的第二個參數(shù)index有關,具體的鍵值生成規(guī)則判斷邏輯如下圖所示。

圖1 ForEach鍵值生成規(guī)則
說明

ArkUI框架會對重復的鍵值發(fā)出警告。在UI更新的場景下,如果出現(xiàn)重復的鍵值,框架可能無法正常工作,具體請參見渲染結果非預期。

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

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

首次渲染

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

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'three'];
  5. build() {
  6. Row() {
  7. Column() {
  8. ForEach(this.simpleList, (item: string) => {
  9. ChildItem({ 'item': item } as Record<string, string>)
  10. }, (item: string) => item)
  11. }
  12. .width('100%')
  13. .height('100%')
  14. }
  15. .height('100%')
  16. .backgroundColor(0xF1F3F5)
  17. }
  18. }
  19. @Component
  20. struct ChildItem {
  21. @Prop item: string;
  22. build() {
  23. Text(this.item)
  24. .fontSize(50)
  25. }
  26. }

運行效果如下圖所示。

圖2 ForEach數(shù)據(jù)源不存在相同值案例首次渲染運行效果圖

在上述代碼中,鍵值生成規(guī)則是keyGenerator函數(shù)的返回值item。在ForEach渲染循環(huán)時,為數(shù)據(jù)源數(shù)組項依次生成鍵值one、two和three,并創(chuàng)建對應的ChildItem組件渲染到界面上。

當不同數(shù)組項按照鍵值生成規(guī)則生成的鍵值相同時,框架的行為是未定義的。例如,在以下代碼中,F(xiàn)orEach渲染相同的數(shù)據(jù)項two時,只創(chuàng)建了一個ChildItem組件,而沒有創(chuàng)建多個具有相同鍵值的組件。

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'two', 'three'];
  5. build() {
  6. Row() {
  7. Column() {
  8. ForEach(this.simpleList, (item: string) => {
  9. ChildItem({ 'item': item } as Record<string, string>)
  10. }, (item: string) => item)
  11. }
  12. .width('100%')
  13. .height('100%')
  14. }
  15. .height('100%')
  16. .backgroundColor(0xF1F3F5)
  17. }
  18. }
  19. @Component
  20. struct ChildItem {
  21. @Prop item: string;
  22. build() {
  23. Text(this.item)
  24. .fontSize(50)
  25. }
  26. }

運行效果如下圖所示。

圖3 ForEach數(shù)據(jù)源存在相同值案例首次渲染運行效果圖

在該示例中,最終鍵值生成規(guī)則為item。當ForEach遍歷數(shù)據(jù)源simpleList,遍歷到索引為1的two時,按照最終鍵值生成規(guī)則生成鍵值為two的組件并進行標記。當遍歷到索引為2的two時,按照最終鍵值生成規(guī)則當前項的鍵值也為two,此時不再創(chuàng)建新的組件。

非首次渲染

在ForEach組件進行非首次渲染時,它會檢查新生成的鍵值是否在上次渲染中已經(jīng)存在。如果鍵值不存在,則會創(chuàng)建一個新的組件;如果鍵值存在,則不會創(chuàng)建新的組件,而是直接渲染該鍵值所對應的組件。例如,在以下的代碼示例中,通過點擊事件修改了數(shù)組的第三項值為"new three",這將觸發(fā)ForEach組件進行非首次渲染。

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'three'];
  5. build() {
  6. Row() {
  7. Column() {
  8. Text('點擊修改第3個數(shù)組項的值')
  9. .fontSize(24)
  10. .fontColor(Color.Red)
  11. .onClick(() => {
  12. this.simpleList[2] = 'new three';
  13. })
  14. ForEach(this.simpleList, (item: string) => {
  15. ChildItem({ item: item })
  16. .margin({ top: 20 })
  17. }, (item: string) => item)
  18. }
  19. .justifyContent(FlexAlign.Center)
  20. .width('100%')
  21. .height('100%')
  22. }
  23. .height('100%')
  24. .backgroundColor(0xF1F3F5)
  25. }
  26. }
  27. @Component
  28. struct ChildItem {
  29. @Prop item: string;
  30. build() {
  31. Text(this.item)
  32. .fontSize(30)
  33. }
  34. }

運行效果如下圖所示。

圖4 ForEach非首次渲染案例運行效果圖

從本例可以看出@State 能夠監(jiān)聽到簡單數(shù)據(jù)類型數(shù)組數(shù)據(jù)源 simpleList 數(shù)組項的變化。

  1. 當 simpleList 數(shù)組項發(fā)生變化時,會觸發(fā) ForEach 進行重新渲染。
  2. ForEach 遍歷新的數(shù)據(jù)源 ['one', 'two', 'new three'],并生成對應的鍵值one、two和new three。
  3. 其中,鍵值one和two在上次渲染中已經(jīng)存在,所以 ForEach 復用了對應的組件并進行了渲染。對于第三個數(shù)組項 "new three",由于其通過鍵值生成規(guī)則 item 生成的鍵值new three在上次渲染中不存在,因此 ForEach 為該數(shù)組項創(chuàng)建了一個新的組件。

使用場景

ForEach組件在開發(fā)過程中的主要應用場景包括:數(shù)據(jù)源不變、數(shù)據(jù)源數(shù)組項發(fā)生變化(如插入、刪除操作)、數(shù)據(jù)源數(shù)組項子屬性變化。

數(shù)據(jù)源不變

在數(shù)據(jù)源保持不變的場景中,數(shù)據(jù)源可以直接采用基本數(shù)據(jù)類型。例如,在頁面加載狀態(tài)時,可以使用骨架屏列表進行渲染展示。

  1. @Entry
  2. @Component
  3. struct ArticleList {
  4. @State simpleList: Array<number> = [1, 2, 3, 4, 5];
  5. build() {
  6. Column() {
  7. ForEach(this.simpleList, (item: string) => {
  8. ArticleSkeletonView()
  9. .margin({ top: 20 })
  10. }, (item: string) => item)
  11. }
  12. .padding(20)
  13. .width('100%')
  14. .height('100%')
  15. }
  16. }
  17. @Builder
  18. function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') {
  19. Row()
  20. .width(width)
  21. .height(height)
  22. .backgroundColor('#FFF2F3F4')
  23. }
  24. @Component
  25. struct ArticleSkeletonView {
  26. build() {
  27. Row() {
  28. Column() {
  29. textArea(80, 80)
  30. }
  31. .margin({ right: 20 })
  32. Column() {
  33. textArea('60%', 20)
  34. textArea('50%', 20)
  35. }
  36. .alignItems(HorizontalAlign.Start)
  37. .justifyContent(FlexAlign.SpaceAround)
  38. .height('100%')
  39. }
  40. .padding(20)
  41. .borderRadius(12)
  42. .backgroundColor('#FFECECEC')
  43. .height(120)
  44. .width('100%')
  45. .justifyContent(FlexAlign.SpaceBetween)
  46. }
  47. }

運行效果如下圖所示。

圖5 骨架屏運行效果圖

在本示例中,采用數(shù)據(jù)項item作為鍵值生成規(guī)則,由于數(shù)據(jù)源simpleList的數(shù)組項各不相同,因此能夠保證鍵值的唯一性。

數(shù)據(jù)源數(shù)組項發(fā)生變化

在數(shù)據(jù)源數(shù)組項發(fā)生變化的場景下,例如進行數(shù)組插入、刪除操作或者數(shù)組項索引位置發(fā)生交換時,數(shù)據(jù)源應為對象數(shù)組類型,并使用對象的唯一ID作為最終鍵值。例如,當在頁面上通過手勢上滑加載下一頁數(shù)據(jù)時,會在數(shù)據(jù)源數(shù)組尾部新增新獲取的數(shù)據(jù)項,從而使得數(shù)據(jù)源數(shù)組長度增大。

  1. @Entry
  2. @Component
  3. struct ArticleListView {
  4. @State isListReachEnd: boolean = false;
  5. @State articleList: Array<Article> = [
  6. new Article('001', '第1篇文章', '文章簡介內(nèi)容'),
  7. new Article('002', '第2篇文章', '文章簡介內(nèi)容'),
  8. new Article('003', '第3篇文章', '文章簡介內(nèi)容'),
  9. new Article('004', '第4篇文章', '文章簡介內(nèi)容'),
  10. new Article('005', '第5篇文章', '文章簡介內(nèi)容'),
  11. new Article('006', '第6篇文章', '文章簡介內(nèi)容')
  12. ]
  13. loadMoreArticles() {
  14. this.articleList.push(new Article('007', '加載的新文章', '文章簡介內(nèi)容'));
  15. }
  16. build() {
  17. Column({ space: 5 }) {
  18. List() {
  19. ForEach(this.articleList, (item: Article) => {
  20. ListItem() {
  21. ArticleCard({ 'article': item } as Record<string, Article>)
  22. .margin({ top: 20 })
  23. }
  24. }, (item: Article) => item.id)
  25. }
  26. .onReachEnd(() => {
  27. this.isListReachEnd = true;
  28. })
  29. .parallelGesture(
  30. PanGesture({ direction: PanDirection.Up, distance: 80 })
  31. .onActionStart(() => {
  32. if (this.isListReachEnd) {
  33. this.loadMoreArticles();
  34. this.isListReachEnd = false;
  35. }
  36. })
  37. )
  38. .padding(20)
  39. .scrollBar(BarState.Off)
  40. }
  41. .width('100%')
  42. .height('100%')
  43. .backgroundColor(0xF1F3F5)
  44. }
  45. }
  46. @Component
  47. struct ArticleCard {
  48. @Prop article: Article;
  49. build() {
  50. Row() {
  51. Image($r('app.media.icon'))
  52. .width(80)
  53. .height(80)
  54. .margin({ right: 20 })
  55. Column() {
  56. Text(this.article.title)
  57. .fontSize(20)
  58. .margin({ bottom: 8 })
  59. Text(this.article.brief)
  60. .fontSize(16)
  61. .fontColor(Color.Gray)
  62. .margin({ bottom: 8 })
  63. }
  64. .alignItems(HorizontalAlign.Start)
  65. .width('80%')
  66. .height('100%')
  67. }
  68. .padding(20)
  69. .borderRadius(12)
  70. .backgroundColor('#FFECECEC')
  71. .height(120)
  72. .width('100%')
  73. .justifyContent(FlexAlign.SpaceBetween)
  74. }
  75. }

初始運行效果(左圖)和手勢上滑加載后效果(右圖)如下圖所示。

圖6 數(shù)據(jù)源數(shù)組項變化案例運行效果圖

在本示例中,ArticleCard組件作為ArticleListView組件的子組件,通過@Prop裝飾器接收一個Article對象,用于渲染文章卡片。

  1. 當列表滾動到底部時,如果手勢滑動距離超過指定的80,將觸發(fā)loadMoreArticle()函數(shù)。此函數(shù)會在articleList數(shù)據(jù)源的尾部添加一個新的數(shù)據(jù)項,從而增加數(shù)據(jù)源的長度。
  2. 數(shù)據(jù)源被@State裝飾器修飾,ArkUI框架能夠感知到數(shù)據(jù)源長度的變化,并觸發(fā)ForEach進行重新渲染。

數(shù)據(jù)源數(shù)組項子屬性變化

當數(shù)據(jù)源的數(shù)組項為對象數(shù)據(jù)類型,并且只修改某個數(shù)組項的屬性值時,由于數(shù)據(jù)源為復雜數(shù)據(jù)類型,ArkUI框架無法監(jiān)聽到@State裝飾器修飾的數(shù)據(jù)源數(shù)組項的屬性變化,從而無法觸發(fā)ForEach的重新渲染。為實現(xiàn)ForEach重新渲染,需要結合@Observed和@ObjectLink裝飾器使用。例如,在文章列表卡片上點擊“點贊”按鈕,從而修改文章的點贊數(shù)量。

  1. @Entry
  2. @Component
  3. struct ArticleListView {
  4. @State articleList: Array<Article> = [
  5. new Article('001', '第0篇文章', '文章簡介內(nèi)容', false, 100),
  6. new Article('002', '第1篇文章', '文章簡介內(nèi)容', false, 100),
  7. new Article('003', '第2篇文章', '文章簡介內(nèi)容', false, 100),
  8. new Article('004', '第4篇文章', '文章簡介內(nèi)容', false, 100),
  9. new Article('005', '第5篇文章', '文章簡介內(nèi)容', false, 100),
  10. new Article('006', '第6篇文章', '文章簡介內(nèi)容', false, 100),
  11. ];
  12. build() {
  13. List() {
  14. ForEach(this.articleList, (item: Article) => {
  15. ListItem() {
  16. ArticleCard({
  17. article: item
  18. })
  19. .margin({ top: 20 })
  20. }
  21. }, (item: Article) => item.id)
  22. }
  23. .padding(20)
  24. .scrollBar(BarState.Off)
  25. .backgroundColor(0xF1F3F5)
  26. }
  27. }
  28. @Component
  29. struct ArticleCard {
  30. @ObjectLink article: Article;
  31. handleLiked() {
  32. this.article.isLiked = !this.article.isLiked;
  33. this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;
  34. }
  35. build() {
  36. Row() {
  37. Image($r('app.media.icon'))
  38. .width(80)
  39. .height(80)
  40. .margin({ right: 20 })
  41. Column() {
  42. Text(this.article.title)
  43. .fontSize(20)
  44. .margin({ bottom: 8 })
  45. Text(this.article.brief)
  46. .fontSize(16)
  47. .fontColor(Color.Gray)
  48. .margin({ bottom: 8 })
  49. Row() {
  50. Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked'))
  51. .width(24)
  52. .height(24)
  53. .margin({ right: 8 })
  54. Text(this.article.likesCount.toString())
  55. .fontSize(16)
  56. }
  57. .onClick(() => this.handleLiked())
  58. .justifyContent(FlexAlign.Center)
  59. }
  60. .alignItems(HorizontalAlign.Start)
  61. .width('80%')
  62. .height('100%')
  63. }
  64. .padding(20)
  65. .borderRadius(12)
  66. .backgroundColor('#FFECECEC')
  67. .height(120)
  68. .width('100%')
  69. .justifyContent(FlexAlign.SpaceBetween)
  70. }
  71. }

上述代碼的初始運行效果(左圖)和點擊第1個文章卡片上的點贊圖標后的運行效果(右圖)如下圖所示。

圖7 數(shù)據(jù)源數(shù)組項子屬性變化案例運行效果圖

在本示例中,Article類被@Observed裝飾器修飾。父組件ArticleListView傳入Article對象實例給子組件ArticleCard,子組件使用@ObjectLink裝飾器接收該實例。

  1. 當點擊第1個文章卡片上的點贊圖標時,會觸發(fā)ArticleCard組件的handleLiked函數(shù)。該函數(shù)修改第1個卡片對應組件里article實例的isLiked和likesCount屬性值。
  2. 由于子組件ArticleCard中的article使用了@ObjectLink裝飾器,父子組件共享同一份article數(shù)據(jù)。因此,父組件中articleList的第1個數(shù)組項的isLiked和likedCounts數(shù)值也會同步修改。
  3. 當父組件監(jiān)聽到數(shù)據(jù)源數(shù)組項屬性值變化時,會觸發(fā)ForEach重新渲染。
  4. 在此處,F(xiàn)orEach鍵值生成規(guī)則為數(shù)組項的id屬性值。當ForEach遍歷新數(shù)據(jù)源時,數(shù)組項的id均沒有變化,不會新建組件。
  5. 渲染第1個數(shù)組項對應的ArticleCard組件時,讀取到的isLiked和likesCount為修改后的新值。

使用建議

  • 盡量避免在最終的鍵值生成規(guī)則中包含數(shù)據(jù)項索引index,以防止出現(xiàn)渲染結果非預期渲染性能降低。如果業(yè)務確實需要使用index,例如列表需要通過index進行條件渲染,開發(fā)者需要接受ForEach在改變數(shù)據(jù)源后重新創(chuàng)建組件所帶來的性能損耗。
  • 為滿足鍵值的唯一性,對于對象數(shù)據(jù)類型,建議使用對象數(shù)據(jù)中的唯一id作為鍵值。
  • 基本數(shù)據(jù)類型的數(shù)據(jù)項沒有唯一ID屬性。如果使用基本數(shù)據(jù)類型本身作為鍵值,必須確保數(shù)組項無重復。因此,對于數(shù)據(jù)源會發(fā)生變化的場景,建議將基本數(shù)據(jù)類型數(shù)組轉化為具備唯一ID屬性的對象數(shù)據(jù)類型數(shù)組,再使用ID屬性作為鍵值生成規(guī)則。

不推薦案例

開發(fā)者在使用ForEach的過程中,若對于鍵值生成規(guī)則的理解不夠充分,可能會出現(xiàn)錯誤的使用方式。錯誤使用一方面會導致功能層面問題,例如渲染結果非預期,另一方面會導致性能層面問題,例如渲染性能降低。

渲染結果非預期

在本示例中,通過設置ForEach的第三個參數(shù)KeyGenerator函數(shù),自定義鍵值生成規(guī)則為數(shù)據(jù)源的索引index的字符串類型值。當點擊父組件Parent中“在第1項后插入新項”文本組件后,界面會出現(xiàn)非預期的結果。

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'three'];
  5. build() {
  6. Column() {
  7. Button() {
  8. Text('在第1項后插入新項').fontSize(30)
  9. }
  10. .onClick(() => {
  11. this.simpleList.splice(1, 0, 'new item');
  12. })
  13. ForEach(this.simpleList, (item: string) => {
  14. ChildItem({ 'item': item } as Record<string, string>)
  15. }, (item: string, index: number) => index.toString())
  16. }
  17. .justifyContent(FlexAlign.Center)
  18. .width('100%')
  19. .height('100%')
  20. .backgroundColor(0xF1F3F5)
  21. }
  22. }
  23. @Component
  24. struct ChildItem {
  25. @Prop item: string;
  26. build() {
  27. Text(this.item)
  28. .fontSize(30)
  29. }
  30. }

上述代碼的初始渲染效果(左圖)和點擊“在第1項后插入新項”文本組件后的渲染效果(右圖)如下圖所示。

圖8 渲染結果非預期運行效果圖

ForEach在首次渲染時,創(chuàng)建的鍵值依次為"0"、"1"、"2"。

插入新項后,數(shù)據(jù)源simpleList變?yōu)閇'one', 'new item', 'two', 'three'],框架監(jiān)聽到@State裝飾的數(shù)據(jù)源長度變化觸發(fā)ForEach重新渲染。

ForEach依次遍歷新數(shù)據(jù)源,遍歷數(shù)據(jù)項"one"時生成鍵值"0",存在相同鍵值,因此不創(chuàng)建新組件。繼續(xù)遍歷數(shù)據(jù)項"new item"時生成鍵值"1",存在相同鍵值,因此不創(chuàng)建新組件。繼續(xù)遍歷數(shù)據(jù)項"two"生成鍵值"2",存在相同鍵值,因此不創(chuàng)建新組件。最后遍歷數(shù)據(jù)項"three"時生成鍵值"3",不存在相同鍵值,創(chuàng)建內(nèi)容為"three"的新組件并渲染。

從以上可以看出,當最終鍵值生成規(guī)則包含index時,期望的界面渲染結果為['one', 'new item', 'two', 'three'],而實際的渲染結果為['one', 'two', 'three', 'three'],渲染結果不符合開發(fā)者預期。因此,開發(fā)者在使用ForEach時應盡量避免最終鍵值生成規(guī)則中包含index。

渲染性能降低

在本示例中,F(xiàn)orEach的第三個參數(shù)KeyGenerator函數(shù)處于缺省狀態(tài)。根據(jù)上述鍵值生成規(guī)則,此例使用框架默認的鍵值生成規(guī)則,即最終鍵值為字符串index + '__' + JSON.stringify(item)。當點擊“在第1項后插入新項”文本組件后,F(xiàn)orEach將需要為第2個數(shù)組項以及其后的所有項重新創(chuàng)建組件。

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'three'];
  5. build() {
  6. Column() {
  7. Button() {
  8. Text('在第1項后插入新項').fontSize(30)
  9. }
  10. .onClick(() => {
  11. this.simpleList.splice(1, 0, 'new item');
  12. console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`);
  13. })
  14. ForEach(this.simpleList, (item: string) => {
  15. ChildItem({ 'item': item } as Record<string, string>)
  16. })
  17. }
  18. .justifyContent(FlexAlign.Center)
  19. .width('100%')
  20. .height('100%')
  21. .backgroundColor(0xF1F3F5)
  22. }
  23. }
  24. @Component
  25. struct ChildItem {
  26. @Prop item: string;
  27. aboutToAppear() {
  28. console.log(`[aboutToAppear]: item is ${this.item}`);
  29. }
  30. build() {
  31. Text(this.item)
  32. .fontSize(50)
  33. }
  34. }

以上代碼的初始渲染效果(左圖)和點擊"在第1項后插入新項"文本組件后的渲染效果(右圖)如下所示。

圖9 渲染性能降低案例運行效果圖

點擊“在第1項后插入新項”文本組件后,IDE的日志打印結果如下所示。

圖10 插入新項后渲染效果

插入新項后,F(xiàn)orEach為new item、 two、 three三個數(shù)組項創(chuàng)建了對應的組件ChildItem,并執(zhí)行了組件的aboutToAppear()生命周期函數(shù)。這是因為:

  1. 在ForEach首次渲染時,創(chuàng)建的鍵值依次為0__one、1__two、2__three。
  2. 插入新項后,數(shù)據(jù)源simpleList變?yōu)閇'one', 'new item', 'two', 'three'],ArkUI框架監(jiān)聽到@State裝飾的數(shù)據(jù)源長度變化觸發(fā)ForEach重新渲染。
  3. ForEach依次遍歷新數(shù)據(jù)源,遍歷數(shù)據(jù)項one時生成鍵值0__one,鍵值已存在,因此不創(chuàng)建新組件。繼續(xù)遍歷數(shù)據(jù)項new item時生成鍵值1__new item,不存在相同鍵值,創(chuàng)建內(nèi)容為new item的新組件并渲染。繼續(xù)遍歷數(shù)據(jù)項two生成鍵值2__two,不存在相同鍵值,創(chuàng)建內(nèi)容為two的新組件并渲染。最后遍歷數(shù)據(jù)項three時生成鍵值3__three,不存在相同鍵值,創(chuàng)建內(nèi)容為three的新組件并渲染。

盡管此示例中界面渲染的結果符合預期,但每次插入一條新數(shù)組項時,F(xiàn)orEach都會為從該數(shù)組項起后面的所有數(shù)組項全部重新創(chuàng)建組件。當數(shù)據(jù)源數(shù)據(jù)量較大或組件結構復雜時,由于組件無法得到復用,將導致性能體驗不佳。因此,除非必要,否則不推薦將第三個參數(shù)KeyGenerator函數(shù)處于缺省狀態(tài),以及在鍵值生成規(guī)則中包含數(shù)據(jù)項索引index。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號