首页 > 编程笔记 > 通用技能 阅读:2

ArkTS @Prop装饰器用法详解(附带实例)

@Prop 装饰器用于将父组件的数据传递给子组件,使得子组件能够接收和使用这些数据。

@Prop 装饰器具有以下特点:
@Prop 和数据源类型需一致,包括简单数据类型同步、数组项同步以及对象属性同步。

嵌套传递层数建议不超过 5 层,以避免性能问题。

允许本地初始化 @Prop 变量,与 @Require 结合时父组件需构造传参。

注意,由 @Prop 装饰的变量在进行深拷贝时,除了基本类型、Map、Set、Date、Array 之外,其他类型会丢失。@Prop 装饰器不适用于使用 @Entry 装饰的自定义组件。

接下来从 @Prop 的不同使用场景来进一步了解其特性。

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

案例实现思路如下:
  1. 在入口文件中用 @State 装饰器装饰一个 count 变量,声明两个按钮用于操作 count 的数据变化,添加一个 Text 文本组件来展示当前组件下的 count 值。
  2. 引入子组件,用于展示,并将 count 作为值传递过去。
  3. 定义一个子组件 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';
          });
      }
    }
  }
}

相关文章