ArkTS @Prop装饰器用法详解(附带实例)
@Prop 装饰器用于将父组件的数据传递给子组件,使得子组件能够接收和使用这些数据。
@Prop 装饰器具有以下特点:
@Prop 和数据源类型需一致,包括简单数据类型同步、数组项同步以及对象属性同步。
嵌套传递层数建议不超过 5 层,以避免性能问题。
允许本地初始化 @Prop 变量,与 @Require 结合时父组件需构造传参。
注意,由 @Prop 装饰的变量在进行深拷贝时,除了基本类型、Map、Set、Date、Array 之外,其他类型会丢失。@Prop 装饰器不适用于使用 @Entry 装饰的自定义组件。
接下来从 @Prop 的不同使用场景来进一步了解其特性。
具体代码实现如下:

图 1 初始效果
单击“父组件加一”按钮,此时父、子组件的 count 值均为 1,效果如下图所示:

图 2 单击父组件按钮
接着单击“子组件减一”按钮,此时发生了变化,父组件的 count 值依旧是 1,但是子组件的 count 值却变成了 0,效果如下图所示。

图 3 单击子组件按钮
因此得出结论,@Prop 装饰器是单向同步的,子组件 @Prop 变量的修改不会同步到父组件的状态变量上。
接下来我们逐步地进行解析:
1) 首先创建一个嵌套类的对象结构,代码如下:
2) 项目文件的结构布局如下图所示:

图 4 项目文件的结构布局
在父组件中,创建了两个按钮用于修改 testObj 这个 ClassB 类,同时为了验证在嵌套场景下,每一层都要用 @Observed 装饰,且每一层都要被 @Prop 接收,这样才能实现数据的动态渲染效果,我们在 text 组件上也添加了单击事件用于修改数据,但这不是必需的,主要为了方便演示。
因为在 ClassB 类中,name 属性并不在嵌套属性内,也就是说当嵌套的属性 title 发生了变化,但是页面没有发生改变时,如果 name 的数据发生了改变,那么页面中嵌套属性的数据也会跟着更新,但这并不是我们想要的效果。
如果想要实现嵌套属性的动态渲染,应该怎么做呢?这里引入了 Child1 这个子组件,并用 @Prop 来接收嵌套的类 ClassA,从而实现动态渲染。
3) 编写子组件并使用 @Prop 来装饰 ClassA,详细代码及解释如下图所示。

图 5 Child子组件
完整代码如下:
@Prop 装饰器具有以下特点:
- 单向下行绑定:父组件的状态变化会同步到子组件的 @Prop 变量,但子组件 @Prop 变量的修改不会回传给父组件;
- 支持类型:Object、class、string、number、boolean、enum、数组,以及这些类型的联合体,如string | number。从 API 11 开始支持 Map、Set 类型;
- 建议对于undefined和null使用显式类型定义,以利用 TypeScript 的类型校验;
- 支持 ArkUI 框架的特定联合类型 Length、ResourceStr、ResourceColor,必须指定类型。
@Prop 和数据源类型需一致,包括简单数据类型同步、数组项同步以及对象属性同步。
嵌套传递层数建议不超过 5 层,以避免性能问题。
允许本地初始化 @Prop 变量,与 @Require 结合时父组件需构造传参。
注意,由 @Prop 装饰的变量在进行深拷贝时,除了基本类型、Map、Set、Date、Array 之外,其他类型会丢失。@Prop 装饰器不适用于使用 @Entry 装饰的自定义组件。
接下来从 @Prop 的不同使用场景来进一步了解其特性。
场景一:父组件@State到子组件@Prop简单数据类型同步
案例实现思路如下:- 在入口文件中用 @State 装饰器装饰一个 count 变量,声明两个按钮用于操作 count 的数据变化,添加一个 Text 文本组件来展示当前组件下的 count 值。
- 引入子组件,用于展示,并将 count 作为值传递过去。
- 定义一个子组件 ChildComponent,子组件中用 @Prop 装饰器接收 count 的值,依旧定义两个按钮用于操作 @Prop 装饰器装饰的 count 数据,同样添加一个 Text 文本组件用于展示当前组件下的 count 值。
具体代码实现如下:
// 子组件 @Component struct ChildComponent { @Prop count: number = 0 build() { Column() { Button('子组件减一') .onClick(() => { this.count -= 1 }) Button('子组件加一') .onClick(() => { this.count += 1 }) Text(`此时子组件的值是:${this.count}`) } } } // 父组件 @Entry @Component struct Demo { // 检测用 @State 装饰的数据被修改后是否会向下流转到子组件 // 检测子组件数据被修改后父组件的数据是否会改变 @State count: number = 0; build() { Column() { Button('父组件减一') .onClick(() => { this.count -= 1 }) Button('父组件加一') .onClick(() => { this.count += 1 }) Text(`此时父组件的值是:${this.count}`) ChildComponent({ count: this.count }) } .justifyContent(FlexAlign.Start) .height('100%') .width('100%) } }初始效果如下图所示:

图 1 初始效果
单击“父组件加一”按钮,此时父、子组件的 count 值均为 1,效果如下图所示:

图 2 单击父组件按钮
接着单击“子组件减一”按钮,此时发生了变化,父组件的 count 值依旧是 1,但是子组件的 count 值却变成了 0,效果如下图所示。

图 3 单击子组件按钮
因此得出结论,@Prop 装饰器是单向同步的,子组件 @Prop 变量的修改不会同步到父组件的状态变量上。
场景二:@Prop嵌套场景
在嵌套场景下,每一层都要用 @Observed 装饰,且每一层都要被@Prop接收,这样数据才能形成响应式。接下来我们逐步地进行解析:
1) 首先创建一个嵌套类的对象结构,代码如下:
// 创建一个嵌套类对象的数据结构 @Observed class ClassA { // 定义一个公共属性 title,类型为字符串 public title: string; // 构造函数,接收一个字符串参数 title,并将其赋值给实例的 title 属性 constructor(title: string) { this.title = title; } } @Observed class ClassB { // 定义一个公共属性 name,类型为字符串 public name: string; // 定义一个公共属性 a,类型为 ClassA 类的实例 public a: ClassA; // 构造函数,接收一个字符串参数 name 和一个 ClassA 类的实例 a,并分别赋值给实例的 name 和 a 属性 constructor(name: string, a: ClassA) { this.name = name; this.a = a; } }
2) 项目文件的结构布局如下图所示:

图 4 项目文件的结构布局
在父组件中,创建了两个按钮用于修改 testObj 这个 ClassB 类,同时为了验证在嵌套场景下,每一层都要用 @Observed 装饰,且每一层都要被 @Prop 接收,这样才能实现数据的动态渲染效果,我们在 text 组件上也添加了单击事件用于修改数据,但这不是必需的,主要为了方便演示。
因为在 ClassB 类中,name 属性并不在嵌套属性内,也就是说当嵌套的属性 title 发生了变化,但是页面没有发生改变时,如果 name 的数据发生了改变,那么页面中嵌套属性的数据也会跟着更新,但这并不是我们想要的效果。
如果想要实现嵌套属性的动态渲染,应该怎么做呢?这里引入了 Child1 这个子组件,并用 @Prop 来接收嵌套的类 ClassA,从而实现动态渲染。
3) 编写子组件并使用 @Prop 来装饰 ClassA,详细代码及解释如下图所示。

图 5 Child子组件
完整代码如下:
// 创建一个嵌套类对象的数据结构 @Observed class ClassA { public title: string; // 定义一个公共属性 title,类型为字符串 constructor(title: string) { this.title = title // 构造函数,接收一个字符串参数 title,并将其赋值给实例的 title 属性 } } @Observed class ClassB { public name: string; // 定义一个公共属性 name,类型为字符串 public a: ClassA; // 定义一个公共属性 a,类型为 ClassA 类的实例 constructor(name: string, a: ClassA) { this.name = name; // 构造函数,接收一个字符串参数 name 和一个 ClassA 类的实例 a,并分别赋值给实例的 name 和 a 属性 this.a = a; } } @Entry @Component struct Demoprop_Observed { @State testObj: ClassB = new ClassB('你好', new ClassA('鸿蒙')); build() { Row() { Column({ space: 10 }) { Button('修改 name') .onClick(() => { this.testObj.name = 'hello'; }); Button('修改 title') .onClick(() => { this.testObj.a.title = 'harmonyos'; }); Text(this.testObj.name) .fontSize(16) .onClick(() => { this.testObj.name = '在 text 处修改 name'; }); Text(this.testObj.a.title) .fontSize(16) .onClick(() => { this.testObj.a.title = '在 text 处修改 title'; }); } } } }