@Prop裝飾器:父子單向同步

2024-01-25 12:03 更新

@Prop裝飾的變量可以和父組件建立單向的同步關(guān)系。@Prop裝飾的變量是可變的,但是變化不會同步回其父組件。

說明

從API version 9開始,該裝飾器支持在ArkTS卡片中使用。

概述

@Prop裝飾的變量和父組件建立單向的同步關(guān)系:

  • @Prop變量允許在本地修改,但修改后的變化不會同步回父組件。
  • 當(dāng)父組件中的數(shù)據(jù)源更改時(shí),與之相關(guān)的@Prop裝飾的變量都會自動更新。如果子組件已經(jīng)在本地修改了@Prop裝飾的相關(guān)變量值,而在父組件中對應(yīng)的@State裝飾的變量被修改后,子組件本地修改的@Prop裝飾的相關(guān)變量值將被覆蓋。

限制條件

@Prop裝飾器不能在@Entry裝飾的自定義組件中使用。

裝飾器使用規(guī)則說明

@Prop變量裝飾器

說明

裝飾器參數(shù)

同步類型

單向同步:對父組件狀態(tài)變量值的修改,將同步給子組件@Prop裝飾的變量,子組件@Prop變量的修改不會同步到父組件的狀態(tài)變量上

允許裝飾的變量類型

string、number、boolean、enum類型。

不支持any,不允許使用undefined和null。

必須指定類型。

在父組件中,傳遞給@Prop裝飾的值不能為undefined或者null,反例如下所示。

CompA ({ aProp: undefined })

CompA ({ aProp: null })

@Prop和數(shù)據(jù)源類型需要相同,有以下三種情況(數(shù)據(jù)源以@State為例):

被裝飾變量的初始值

允許本地初始化。

變量的傳遞/訪問規(guī)則說明

傳遞/訪問

說明

從父組件初始化

如果本地有初始化,則是可選的。沒有的話,則必選,支持父組件中的常規(guī)變量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp去初始化子組件中的@Prop變量。

用于初始化子組件

@Prop支持去初始化子組件中的常規(guī)變量、@State、@Link、@Prop、@Provide。

是否支持組件外訪問

@Prop裝飾的變量是私有的,只能在組件內(nèi)訪問。

圖1 初始化規(guī)則圖示

觀察變化和行為表現(xiàn)

觀察變化

@Prop裝飾的數(shù)據(jù)可以觀察到以下變化。

  • 當(dāng)裝飾的類型是允許的類型,即string、number、boolean、enum類型都可以觀察到的賦值變化;
    1. // 簡單類型
    2. @Prop count: number;
    3. // 賦值的變化可以被觀察到
    4. this.count = 1;

對于@State和@Prop的同步場景:

  • 使用父組件中@State變量的值初始化子組件中的@Prop變量。當(dāng)@State變量變化時(shí),該變量值也會同步更新至@Prop變量。
  • @Prop裝飾的變量的修改不會影響其數(shù)據(jù)源@State裝飾變量的值。
  • 除了@State,數(shù)據(jù)源也可以用@Link或@Prop裝飾,對@Prop的同步機(jī)制是相同的。
  • 數(shù)據(jù)源和@Prop變量的類型需要相同。

框架行為

要理解@Prop變量值初始化和更新機(jī)制,有必要了解父組件和擁有@Prop變量的子組件初始渲染和更新流程。

  1. 初始渲染:
    1. 執(zhí)行父組件的build()函數(shù)將創(chuàng)建子組件的新實(shí)例,將數(shù)據(jù)源傳遞給子組件;
    2. 初始化子組件@Prop裝飾的變量。
  2. 更新:
    1. 子組件@Prop更新時(shí),更新僅停留在當(dāng)前子組件,不會同步回父組件;
    2. 當(dāng)父組件的數(shù)據(jù)源更新時(shí),子組件的@Prop裝飾的變量將被來自父組件的數(shù)據(jù)源重置,所有@Prop裝飾的本地的修改將被父組件的更新覆蓋。

使用場景

父組件@State到子組件@Prop簡單數(shù)據(jù)類型同步

以下示例是@State到子組件@Prop簡單數(shù)據(jù)同步,父組件ParentComponent的狀態(tài)變量countDownStartValue初始化子組件CountDownComponent中@Prop裝飾的count,點(diǎn)擊“Try again”,count的修改僅保留在CountDownComponent,不會同步給父組件ParentComponent。

ParentComponent的狀態(tài)變量countDownStartValue的變化將重置CountDownComponent的count。

  1. @Component
  2. struct CountDownComponent {
  3. @Prop count: number;
  4. costOfOneAttempt: number = 1;
  5. build() {
  6. Column() {
  7. if (this.count > 0) {
  8. Text(`You have ${this.count} Nuggets left`)
  9. } else {
  10. Text('Game over!')
  11. }
  12. // @Prop裝飾的變量不會同步給父組件
  13. Button(`Try again`).onClick(() => {
  14. this.count -= this.costOfOneAttempt;
  15. })
  16. }
  17. }
  18. }
  19. @Entry
  20. @Component
  21. struct ParentComponent {
  22. @State countDownStartValue: number = 10;
  23. build() {
  24. Column() {
  25. Text(`Grant ${this.countDownStartValue} nuggets to play.`)
  26. // 父組件的數(shù)據(jù)源的修改會同步給子組件
  27. Button(`+1 - Nuggets in New Game`).onClick(() => {
  28. this.countDownStartValue += 1;
  29. })
  30. // 父組件的修改會同步給子組件
  31. Button(`-1 - Nuggets in New Game`).onClick(() => {
  32. this.countDownStartValue -= 1;
  33. })
  34. CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
  35. }
  36. }
  37. }

在上面的示例中:

  1. CountDownComponent子組件首次創(chuàng)建時(shí)其@Prop裝飾的count變量將從父組件@State裝飾的countDownStartValue變量初始化;
  2. 按“+1”或“-1”按鈕時(shí),父組件的@State裝飾的countDownStartValue值會變化,這將觸發(fā)父組件重新渲染,在父組件重新渲染過程中會刷新使用countDownStartValue狀態(tài)變量的UI組件并單向同步更新CountDownComponent子組件中的count值;
  3. 更新count狀態(tài)變量值也會觸發(fā)CountDownComponent的重新渲染,在重新渲染過程中,評估使用count狀態(tài)變量的if語句條件(this.count > 0),并執(zhí)行true分支中的使用count狀態(tài)變量的UI組件相關(guān)描述來更新Text組件的UI顯示;
  4. 當(dāng)按下子組件CountDownComponent的“Try again”按鈕時(shí),其@Prop變量count將被更改,但是count值的更改不會影響父組件的countDownStartValue值;
  5. 父組件的countDownStartValue值會變化時(shí),父組件的修改將覆蓋掉子組件CountDownComponent中count本地的修改。

父組件@State數(shù)組項(xiàng)到子組件@Prop簡單數(shù)據(jù)類型同步

父組件中@State如果裝飾的數(shù)組,其數(shù)組項(xiàng)也可以初始化@Prop。以下示例中父組件Index中@State裝飾的數(shù)組arr,將其數(shù)組項(xiàng)初始化子組件Child中@Prop裝飾的value。

  1. @Component
  2. struct Child {
  3. @Prop value: number;
  4. build() {
  5. Text(`${this.value}`)
  6. .fontSize(50)
  7. .onClick(()=>{this.value++})
  8. }
  9. }
  10. @Entry
  11. @Component
  12. struct Index {
  13. @State arr: number[] = [1,2,3];
  14. build() {
  15. Row() {
  16. Column() {
  17. Child({value: this.arr[0]})
  18. Child({value: this.arr[1]})
  19. Child({value: this.arr[2]})
  20. Divider().height(5)
  21. ForEach(this.arr,
  22. item => {
  23. Child({'value': item} as Record<string, number>)
  24. },
  25. item => item.toString()
  26. )
  27. Text('replace entire arr')
  28. .fontSize(50)
  29. .onClick(()=>{
  30. // 兩個(gè)數(shù)組都包含項(xiàng)“3”。
  31. this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
  32. })
  33. }
  34. }
  35. }
  36. }

初始渲染創(chuàng)建6個(gè)子組件實(shí)例,每個(gè)@Prop裝飾的變量初始化都在本地拷貝了一份數(shù)組項(xiàng)。子組件onclick事件處理程序會更改局部變量值。

如果點(diǎn)擊界面上的“1”六次、“2”五次、“3”四次,將所有變量的本地取值都變?yōu)椤?”。

  1. 7
  2. 7
  3. 7
  4. ----
  5. 7
  6. 7
  7. 7

單擊replace entire arr后,屏幕將顯示以下信息,為什么?

  1. 3
  2. 4
  3. 5
  4. ----
  5. 7
  6. 4
  7. 5
  • 在子組件Child中做的所有的修改都不會同步回父組件Index組件,所以即使6個(gè)組件顯示都為7,但在父組件Index中,this.arr保存的值依舊是[1,2,3]。
  • 點(diǎn)擊replace entire arr,this.arr[0] == 1成立,將this.arr賦值為[3, 4, 5];
  • 因?yàn)閠his.arr[0]已更改,Child({value: this.arr[0]})組件將this.arr[0]更新同步到實(shí)例@Prop裝飾的變量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情況也類似。
  • this.arr的更改觸發(fā)ForEach更新,this.arr更新的前后都有數(shù)值為3的數(shù)組項(xiàng):[3, 4, 5] 和[1, 2, 3]。根據(jù)diff算法,數(shù)組項(xiàng)“3”將被保留,刪除“1”和“2”的數(shù)組項(xiàng),添加為“4”和“5”的數(shù)組項(xiàng)。這就意味著,數(shù)組項(xiàng)“3”的組件不會重新生成,而是將其移動到第一位。所以“3”對應(yīng)的組件不會更新,此時(shí)“3”對應(yīng)的組件數(shù)值為“7”,F(xiàn)orEach最終的渲染結(jié)果是“7”,“4”,“5”。

從父組件中的@State類對象屬性到@Prop簡單類型的同步

如果圖書館有一本圖書和兩位用戶,每位用戶都可以將圖書標(biāo)記為已讀,此標(biāo)記行為不會影響其它讀者用戶。從代碼角度講,對@Prop圖書對象的本地更改不會同步給圖書館組件中的@State圖書對象。

  1. class Book {
  2. public title: string;
  3. public pages: number;
  4. public readIt: boolean = false;
  5. constructor(title: string, pages: number) {
  6. this.title = title;
  7. this.pages = pages;
  8. }
  9. }
  10. @Component
  11. struct ReaderComp {
  12. @Prop title: string;
  13. @Prop readIt: boolean;
  14. build() {
  15. Row() {
  16. Text(this.title)
  17. Text(`... ${this.readIt ? 'I have read' : 'I have not read it'}`)
  18. .onClick(() => this.readIt = true)
  19. }
  20. }
  21. }
  22. @Entry
  23. @Component
  24. struct Library {
  25. @State book: Book = new Book('100 secrets of C++', 765);
  26. build() {
  27. Column() {
  28. ReaderComp({ title: this.book.title, readIt: this.book.readIt })
  29. ReaderComp({ title: this.book.title, readIt: this.book.readIt })
  30. }
  31. }
  32. }

@Prop本地初始化不和父組件同步

為了支持@Component裝飾的組件復(fù)用場景,@Prop支持本地初始化,這樣可以讓@Prop是否與父組件建立同步關(guān)系變得可選。當(dāng)且僅當(dāng)@Prop有本地初始化時(shí),從父組件向子組件傳遞@Prop的數(shù)據(jù)源才是可選的。

下面的示例中,子組件包含兩個(gè)@Prop變量:

  • @Prop customCounter沒有本地初始化,所以需要父組件提供數(shù)據(jù)源去初始化@Prop,并當(dāng)父組件的數(shù)據(jù)源變化時(shí),@Prop也將被更新;
  • @Prop customCounter2有本地初始化,在這種情況下,@Prop依舊允許但非強(qiáng)制父組件同步數(shù)據(jù)源給@Prop。
    1. @Component
    2. struct MyComponent {
    3. @Prop customCounter: number;
    4. @Prop customCounter2: number = 5;
    5. build() {
    6. Column() {
    7. Row() {
    8. Text(`From Main: ${this.customCounter}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
    9. }
    10. Row() {
    11. Button('Click to change locally !')
    12. .width(288)
    13. .height(40)
    14. .margin({ left: 30, top: 12 })
    15. .fontColor('#FFFFFF,90%')
    16. .onClick(() => {
    17. this.customCounter2++
    18. })
    19. }
    20. Row() {
    21. Text(`Custom Local: ${this.customCounter2}`).fontColor('#ff6b6565').margin({ left: -110, top: 12 })
    22. }
    23. }
    24. }
    25. }
    26. @Entry
    27. @Component
    28. struct MainProgram {
    29. @State mainCounter: number = 10;
    30. build() {
    31. Column() {
    32. Row() {
    33. Column() {
    34. // customCounter必須從父組件初始化,因?yàn)镸yComponent的customCounter成員變量缺少本地初始化;此處,customCounter2可以不做初始化。
    35. MyComponent({ customCounter: this.mainCounter })
    36. // customCounter2也可以從父組件初始化,父組件初始化的值會覆蓋子組件customCounter2的本地初始化的值
    37. MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
    38. }
    39. }
    40. Row() {
    41. Column() {
    42. Button('Click to change number')
    43. .width(288)
    44. .height(40)
    45. .margin({ left: 30, top: 12 })
    46. .fontColor('#FFFFFF,90%')
    47. .onClick(() => {
    48. this.mainCounter++
    49. })
    50. }
    51. }
    52. }
    53. }
    54. }

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號