ForEach:循環(huán)渲染

2024-01-25 12:08 更新

ForEach接口基于數(shù)組類型數(shù)據(jù)來進(jìn)行循環(huán)渲染,需要與容器組件配合使用,且接口返回的組件應(yīng)當(dāng)是允許包含在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è)置為空數(shù)組,此時(shí)不會(huì)創(chuàng)建子組件。

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

itemGenerator

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

組件生成函數(shù)。

- 為數(shù)組中的每個(gè)元素創(chuàng)建對(duì)應(yīng)的組件。

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

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

說明

- 組件的類型必須是ForEach的父容器所允許的。例如,ListItem組件要求ForEach的父容器組件必須為L(zhǎng)ist組件。

keyGenerator

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

鍵值生成函數(shù)。

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

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

說明

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

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

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

鍵值生成規(guī)則

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

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

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

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

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

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

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

首次渲染

在ForEach首次渲染時(shí),會(huì)根據(jù)前述鍵值生成規(guī)則為數(shù)據(jù)源的每個(gè)數(shù)組項(xiàng)生成唯一鍵值,并創(chuàng)建相應(yī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. }

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

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

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

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

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

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

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

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

非首次渲染

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

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'three'];
  5. build() {
  6. Row() {
  7. Column() {
  8. Text('點(diǎn)擊修改第3個(gè)數(shù)組項(xiàng)的值')
  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. }

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

圖4 ForEach非首次渲染案例運(yùn)行效果圖

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

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

使用場(chǎng)景

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

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

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

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

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

圖5 骨架屏運(yùn)行效果圖

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

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

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

  1. @Entry
  2. @Component
  3. struct ArticleListView {
  4. @State isListReachEnd: boolean = false;
  5. @State articleList: Array<Article> = [
  6. new Article('001', '第1篇文章', '文章簡(jiǎn)介內(nèi)容'),
  7. new Article('002', '第2篇文章', '文章簡(jiǎn)介內(nèi)容'),
  8. new Article('003', '第3篇文章', '文章簡(jiǎn)介內(nèi)容'),
  9. new Article('004', '第4篇文章', '文章簡(jiǎn)介內(nèi)容'),
  10. new Article('005', '第5篇文章', '文章簡(jiǎn)介內(nèi)容'),
  11. new Article('006', '第6篇文章', '文章簡(jiǎn)介內(nèi)容')
  12. ]
  13. loadMoreArticles() {
  14. this.articleList.push(new Article('007', '加載的新文章', '文章簡(jiǎn)介內(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. }

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

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

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

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

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

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

  1. @Entry
  2. @Component
  3. struct ArticleListView {
  4. @State articleList: Array<Article> = [
  5. new Article('001', '第0篇文章', '文章簡(jiǎn)介內(nèi)容', false, 100),
  6. new Article('002', '第1篇文章', '文章簡(jiǎn)介內(nèi)容', false, 100),
  7. new Article('003', '第2篇文章', '文章簡(jiǎn)介內(nèi)容', false, 100),
  8. new Article('004', '第4篇文章', '文章簡(jiǎn)介內(nèi)容', false, 100),
  9. new Article('005', '第5篇文章', '文章簡(jiǎn)介內(nèi)容', false, 100),
  10. new Article('006', '第6篇文章', '文章簡(jiǎn)介內(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. }

上述代碼的初始運(yùn)行效果(左圖)和點(diǎn)擊第1個(gè)文章卡片上的點(diǎn)贊圖標(biāo)后的運(yùn)行效果(右圖)如下圖所示。

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

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

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

使用建議

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

不推薦案例

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

渲染結(jié)果非預(yù)期

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

  1. @Entry
  2. @Component
  3. struct Parent {
  4. @State simpleList: Array<string> = ['one', 'two', 'three'];
  5. build() {
  6. Column() {
  7. Button() {
  8. Text('在第1項(xiàng)后插入新項(xiàng)').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. }

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

圖8 渲染結(jié)果非預(yù)期運(yùn)行效果圖

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

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

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

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

渲染性能降低

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

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

圖9 渲染性能降低案例運(yùn)行效果圖

點(diǎn)擊“在第1項(xiàng)后插入新項(xiàng)”文本組件后,IDE的日志打印結(jié)果如下所示。

圖10 插入新項(xiàng)后渲染效果

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

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

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

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)