@Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
说明:
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
概述
@Prop装饰的变量和父组件建立单向的同步关系:
-
@Prop变量允许在本地修改,但修改后的变化不会同步回父组件。
-
当数据源更改时,@Prop装饰的变量都会更新,并且会覆盖本地所有更改。因此,数值的同步是父组件到子组件(所属组件),子组件数值的变化不会同步到父组件。
限制条件
-
@Prop装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型。例如PixelMap 等通过NAPI提供的复杂类型,由于有部分实现在Native侧,因此无法在ArkTS侧通过深拷贝获得完整的数据。
-
@Prop装饰器不能在@Entry装饰的自定义组件中使用。
装饰器使用规则说明
@Prop变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
同步类型 | 单向同步:对父组件状态变量值的修改,将同步给子组件@Prop装饰的变量,子组件@Prop变量的修改不会同步到父组件的状态变量上。嵌套类型的场景请参考 观察变化 。 |
允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。 不支持any,支持undefined和null。 支持Date类型。 API11及以上支持Map、Set类型。 支持类型的场景请参考 观察变化 。 API11及以上支持上述支持类型的联合类型,比如string | number, string | undefined 或者 ClassA | null,示例见 Prop支持联合类型实例 。 注意 当使用undefined和null的时候,建议显式指定类型,遵循TypeScipt类型校验,比如: @Prop a : string | undefined = undefined 是推荐的,不推荐@Prop a: string = undefined 。 |
支持AkrUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。 | 必须指定类型。 @Prop和 数据源 类型需要相同,有以下三种情况: - @Prop装饰的变量和@State以及其他装饰器同步时双方的类型必须相同,示例请参考 父组件@State到子组件@Prop简单数据类型同步 。 - @Prop装饰的变量和@State以及其他装饰器装饰的数组的项同步时 ,@Prop的类型需要和@State装饰的数组的数组项相同,比如@Prop : T和@State : Array<T>,示例请参考 父组件@State数组中的项到子组件@Prop简单数据类型同步 。 - 当父组件状态变量为Object或者class时,@Prop装饰的变量和父组件状态变量的属性类型相同,示例请参考 从父组件中的@State类对象属性到@Prop简单类型的同步 。 |
嵌套传递层数 | 在组件复用场景,建议@Prop深度嵌套数据不要超过5层,嵌套太多会导致深拷贝占用的空间过大以及GarbageCollection(垃圾回收),引起性能问题,此时更建议使用 @ObjectLink 。 |
被装饰变量的初始值 | 允许本地初始化。如果在API 11中和 @Require 结合使用,则必须父组件构造传参。 |
变量的传递/访问规则说明
传递/访问 | 说明 |
---|---|
从父组件初始化 | 如果本地有初始化,则是可选的。没有的话,则必选,支持父组件中的常规变量(常规变量对@Prop赋值,只是数值的初始化,常规变量的变化不会触发UI刷新。只有状态变量才能触发UI刷新)、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp去初始化子组件中的@Prop变量。 |
用于初始化子组件 | @Prop支持去初始化子组件中的常规变量、@State、@Link、@Prop、@Provide。 |
是否支持组件外访问 | @Prop装饰的变量是私有的,只能在组件内访问。 |
图1 初始化规则图示
观察变化和行为表现
观察变化
@Prop装饰的数据可以观察到以下变化。
-
当装饰的类型是允许的类型,即Object、class、string、number、boolean、enum类型都可以观察到赋值的变化。
// 简单类型 @Prop count: number; // 赋值的变化可以被观察到 this.count = 1; // 复杂类型 @Prop title: Model; // 可以观察到赋值的变化 this.title = new Model('Hi');
-
当装饰的类型是Object或者class复杂类型时,可以观察到第一层的属性的变化,属性即Object.keys(observedObject)返回的所有属性;
class ClassA {
public value: string;
constructor(value: string) {
this.value = value;
}
}
class Model {
public value: string;
public a: ClassA;
constructor(value: string, a: ClassA) {
this.value = value;
this.a = a;
}
}
@Prop title: Model;
// 可以观察到第一层的变化
this.title.value = 'Hi'
// 观察不到第二层的变化
this.title.a.value = 'ArkUi'
对于嵌套场景,如果class是被@Observed装饰的,可以观察到class属性的变化,示例请参考@Prop嵌套场景。
- 当装饰的类型是数组的时候,可以观察到数组本身的赋值和数组项的添加、删除和更新。
// @State装饰的对象为数组时
@Prop title: string[]
// 数组自身的赋值可以观察到
this.title = ['1']
// 数组项的赋值可以观察到
this.title[0] = '2'
// 删除数组项可以观察到
this.title.pop()
// 新增数组项可以观察到
this.title.push('3')
对于@State和@Prop的同步场景:
-
使用父组件中@State变量的值初始化子组件中的@Prop变量。当@State变量变化时,该变量值也会同步更新至@Prop变量。
-
@Prop装饰的变量的修改不会影响其数据源@State装饰变量的值。
-
除了@State,数据源也可以用@Link或@Prop装饰,对@Prop的同步机制是相同的。
-
数据源和@Prop变量的类型需要相同,@Prop允许简单类型和class类型。
-
当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口
setFullYear
,setMonth
,setDate
,setHours
,setMinutes
,setSeconds
,setMilliseconds
,setTime
,setUTCFullYear
,setUTCMonth
,setUTCDate
,setUTCHours
,setUTCMinutes
,setUTCSeconds
,setUTCMilliseconds
更新Date的属性。
@Component
struct DateComponent {
@Prop selectedDate: Date = new Date('');
build() {
Column() {
Button('child update the new date')
.margin(10)
.onClick(() => {
this.selectedDate = new Date('2023-09-09')
})
Button(`child increase the year by 1`).onClick(() => {
this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1)
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.selectedDate
})
}
}
}
@Entry
@Component
struct ParentComponent {
@State parentSelectedDate: Date = new Date('2021-08-08');
build() {
Column() {
Button('parent update the new date')
.margin(10)
.onClick(() => {
this.parentSelectedDate = new Date('2023-07-07')
})
Button('parent increase the day by 1')
.margin(10)
.onClick(() => {
this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1)
})
DatePicker({
start: new Date('1970-1-1'),
end: new Date('2100-1-1'),
selected: this.parentSelectedDate
})
DateComponent({
selectedDate: this.parentSelectedDate })
}
}
}
-
当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口
set
,clear
,delete
更新Map的值。 -
当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口
add
,clear
,delete
更新Set的值。
框架行为
要理解@Prop变量值初始化和更新机制,有必要了解父组件和拥有@Prop变量的子组件初始渲染和更新流程。
-
初始渲染:
- 执行父组件的build()函数将创建子组件的新实例,将数据源传递给子组件;
- 初始化子组件@Prop装饰的变量。
-
更新:
- 子组件@Prop更新时,更新仅停留在当前子组件,不会同步回父组件;
- 当父组件的数据源更新时,子组件的@Prop装饰的变量将被来自父组件的数据源重置,所有@Prop装饰的本地的修改将被父组件的更新覆盖。
说明:
@Prop装饰的数据更新依赖其所属自定义组件的重新渲染,所以在应用进入后台后,@Prop无法刷新,推荐使用@Link代替。
使用场景
父组件@State到子组件@Prop简单数据类型同步
以下示例是@State到子组件@Prop简单数据同步,父组件ParentComponent的状态变量countDownStartValue初始化子组件CountDownComponent中@Prop装饰的count,点击“Try again”,count的修改仅保留在CountDownComponent 不会同步给父组件ParentComponent。
ParentComponent的状态变量countDownStartValue的变化将重置CountDownComponent的count。
@Component
struct CountDownComponent {
@Prop count: number = 0;
costOfOneAttempt: number = 1;
build() {
Column() {
if (this.count > 0) {
Text(`You have ${
this.count} Nuggets left`)
} else {
Text('Game over!')
}
// @Prop装饰的变量不会同步给父组件
Button(`Try again`).onClick(() => {
this.count -= this.costOfOneAttempt;
})
}
}
}
@Entry
@Component
struct ParentComponent {
@State countDownStartValue: number = 10;
build() {
Column() {
Text(`Grant ${
this.countDownStartValue} nuggets to play.`)
// 父组件的数据源的修改会同步给子组件
Button(`+1 - Nuggets in New Game`).onClick(() => {
this.countDownStartValue += 1;
})
// 父组件的修改会同步给子组件
Button(`-1 - Nuggets in New Game`).onClick(() => {
this.countDownStartValue -= 1;
})
CountDownComponent({
count: this.countDownStartValue, costOfOneAttempt: 2 })
}
}
}
在上面的示例中:
-
CountDownComponent子组件首次创建时其@Prop装饰的count变量将从父组件@State装饰的countDownStartValue变量初始化;
-
按“+1”或“-1”按钮时,父组件的@State装饰的countDownStartValue值会变化,这将触发父组件重新渲染,在父组件重新渲染过程中会刷新使用countDownStartValue状态变量的UI组件并单向同步更新CountDownComponent子组件中的count值;
-
更新count状态变量值也会触发CountDownComponent的重新渲染,在重新渲染过程中,评估使用count状态变量的if语句条件(this.count > 0),并执行true分支中的使用count状态变量的UI组件相关描述来更新Text组件的UI显示;
-
当按下子组件CountDownComponent的“Try again”按钮时,其@Prop变量count将被更改,但是count值的更改不会影响父组件的countDownStartValue值;
-
父组件的countDownStartValue值会变化时,父组件的修改将覆盖掉子组件CountDownComponent中count本地的修改。
父组件@State数组项到子组件@Prop简单数据类型同步
父组件中@State如果装饰的数组,其数组项也可以初始化@Prop。以下示例中父组件Index中@State装饰的数组arr,将其数组项初始化子组件Child中@Prop装饰的value。
@Component
struct Child {
@Prop value: number = 0;
build() {
Text(`${
this.value}`)
.fontSize(50)
.onClick(() => {
this.value++
})
}
}
@Entry
@Component
struct Index {
@State arr: number[] = [1, 2, 3];
build() {
Row() {
Column() {
Child({
value: this.arr[0] })
Child(