@Prop装饰器:父子单向同步

2024-01-25 12:03 更新

@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

概述

@Prop装饰的变量和父组件建立单向的同步关系:

  • @Prop变量允许在本地修改,但修改后的变化不会同步回父组件。
  • 当父组件中的数据源更改时,与之相关的@Prop装饰的变量都会自动更新。如果子组件已经在本地修改了@Prop装饰的相关变量值,而在父组件中对应的@State装饰的变量被修改后,子组件本地修改的@Prop装饰的相关变量值将被覆盖。

限制条件

@Prop装饰器不能在@Entry装饰的自定义组件中使用。

装饰器使用规则说明

@Prop变量装饰器

说明

装饰器参数

同步类型

单向同步:对父组件状态变量值的修改,将同步给子组件@Prop装饰的变量,子组件@Prop变量的修改不会同步到父组件的状态变量上

允许装饰的变量类型

string、number、boolean、enum类型。

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

必须指定类型。

在父组件中,传递给@Prop装饰的值不能为undefined或者null,反例如下所示。

CompA ({ aProp: undefined })

CompA ({ aProp: null })

@Prop和数据源类型需要相同,有以下三种情况(数据源以@State为例):

被装饰变量的初始值

允许本地初始化。

变量的传递/访问规则说明

传递/访问

说明

从父组件初始化

如果本地有初始化,则是可选的。没有的话,则必选,支持父组件中的常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp去初始化子组件中的@Prop变量。

用于初始化子组件

@Prop支持去初始化子组件中的常规变量、@State、@Link、@Prop、@Provide。

是否支持组件外访问

@Prop装饰的变量是私有的,只能在组件内访问。

图1 初始化规则图示

观察变化和行为表现

观察变化

@Prop装饰的数据可以观察到以下变化。

  • 当装饰的类型是允许的类型,即string、number、boolean、enum类型都可以观察到的赋值变化;
    1. // 简单类型
    2. @Prop count: number;
    3. // 赋值的变化可以被观察到
    4. this.count = 1;

对于@State和@Prop的同步场景:

  • 使用父组件中@State变量的值初始化子组件中的@Prop变量。当@State变量变化时,该变量值也会同步更新至@Prop变量。
  • @Prop装饰的变量的修改不会影响其数据源@State装饰变量的值。
  • 除了@State,数据源也可以用@Link或@Prop装饰,对@Prop的同步机制是相同的。
  • 数据源和@Prop变量的类型需要相同。

框架行为

要理解@Prop变量值初始化和更新机制,有必要了解父组件和拥有@Prop变量的子组件初始渲染和更新流程。

  1. 初始渲染:
    1. 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件;
    2. 初始化子组件@Prop装饰的变量。
  2. 更新:
    1. 子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件;
    2. 当父组件的数据源更新时,子组件的@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰的本地的修改将被父组件的更新覆盖。

使用场景

父组件@State到子组件@Prop简单数据类型同步

以下示例是@State到子组件@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent,不会同步给父组件ParentComponent。

ParentComponent的状态变量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. // 父组件的数据源的修改会同步给子组件
  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子组件首次创建时其@Prop装饰的count变量将从父组件@State装饰的countDownStartValue变量初始化;
  2. 按“+1”或“-1”按钮时,父组件的@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件并单向同步更新CountDownComponent子组件中的count值;
  3. 更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count > 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示;
  4. 当按下子组件CountDownComponent的“Try again”按钮时,其@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值;
  5. 父组件的countDownStartValue值会变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。

父组件@State数组项到子组件@Prop简单数据类型同步

父组件中@State如果装饰的数组,其数组项也可以初始化@Prop。以下示例中父组件Index中@State装饰的数组arr,将其数组项初始化子组件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. // 两个数组都包含项“3”。
  31. this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
  32. })
  33. }
  34. }
  35. }
  36. }

初始渲染创建6个子组件实例,每个@Prop装饰的变量初始化都在本地拷贝了一份数组项。子组件onclick事件处理程序会更改局部变量值。

如果点击界面上的“1”六次、“2”五次、“3”四次,将所有变量的本地取值都变为“7”。

  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个组件显示都为7,但在父组件Index中,this.arr保存的值依旧是[1,2,3]。
  • 点击replace entire arr,this.arr[0] == 1成立,将this.arr赋值为[3, 4, 5];
  • 因为this.arr[0]已更改,Child({value: this.arr[0]})组件将this.arr[0]更新同步到实例@Prop装饰的变量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情况也类似。
  • this.arr的更改触发ForEach更新,this.arr更新的前后都有数值为3的数组项:[3, 4, 5] 和[1, 2, 3]。根据diff算法,数组项“3”将被保留,删除“1”和“2”的数组项,添加为“4”和“5”的数组项。这就意味着,数组项“3”的组件不会重新生成,而是将其移动到第一位。所以“3”对应的组件不会更新,此时“3”对应的组件数值为“7”,ForEach最终的渲染结果是“7”,“4”,“5”。

从父组件中的@State类对象属性到@Prop简单类型的同步

如果图书馆有一本图书和两位用户,每位用户都可以将图书标记为已读,此标记行为不会影响其它读者用户。从代码角度讲,对@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装饰的组件复用场景,@Prop支持本地初始化,这样可以让@Prop是否与父组件建立同步关系变得可选。当且仅当@Prop有本地初始化时,从父组件向子组件传递@Prop的数据源才是可选的。

下面的示例中,子组件包含两个@Prop变量:

  • @Prop customCounter没有本地初始化,所以需要父组件提供数据源去初始化@Prop,并当父组件的数据源变化时,@Prop也将被更新;
  • @Prop customCounter2有本地初始化,在这种情况下,@Prop依旧允许但非强制父组件同步数据源给@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必须从父组件初始化,因为MyComponent的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. }

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号