一、核心区别

特性 @Prop @Link
数据流向 单向:父 → 子 双向:父 ⇄ 子
子组件能否修改 可以修改,但不会同步回父组件 修改会立即同步回父组件
初始值来源 父组件传递的值或本地默认值 必须由父组件传递 $ 绑定的变量
传递方式 值拷贝(深拷贝,对对象类型会递归复制) 引用传递(建立双向绑定)
性能 父组件变化时,子组件会重新渲染 任意一端变化,两端都会重新渲染
适用场景 子组件仅需展示父组件数据,或内部临时修改不影响外部 子组件需要修改父组件数据(如表单、开关)
  • @Prop 装饰器的作用:父组件将数据单向传递给子组件,子组件通过 @Prop 接收后,可以将其视为自己的局部状态进行修改,但这些修改不会影响父组件。当父组件的源数据变化时,子组件的 @Prop 会自动更新(重新赋值并触发渲染)。
  • @Prop 装饰器的使用规则:
    • 子组件中 @Prop 变量可以初始化默认值(如果父组件未传,则使用默认值)。
    • 父组件在子组件标签中直接传递普通变量(无需 $ 符号)。
    • 对于对象类型,@Prop 会进行深拷贝,因此子组件内部修改对象的属性不会影响父组件对象。
  • @Link 装饰器的作用:建立父子组件之间的双向同步,子组件通过 @Link 接收父组件的状态,无论哪一方修改该值,另一方都会自动更新。
  • @Link 的使用规则:
    • 子组件中 @Link 变量不能初始化默认值,必须由父组件传递。
    • 父组件在子组件标签中必须传递被 $ 符号包裹的变量(如 $parentState),表示建立双向绑定。
    • @Link 传递的是引用,因此子组件内修改对象的属性会直接反映到父组件。

二、场景使用

  • 场景示例:父组件有一个计数器,子组件可以显示并修改它。
  • 使用 @Prop(单向):
// 父组件
@Entry
@Component
struct Parent {
  @State count: number = 10;

  build() {
    Column() {
      Text(`父组件 count: ${this.count}`)
        .fontSize(20)
        .margin(10)
      Button('父组件 +1')
        .onClick(() => {
          this.count++;
        })
      Divider()
      ChildProp({ childCount: this.count })
    }
    .width('100%')
    .padding(20)
  }
}

// 子组件
@Component
struct ChildProp {
  @Prop childCount: number;  // 单向接收

  build() {
    Column() {
      Text(`子组件 childCount: ${this.childCount}`)
        .fontSize(20)
        .margin(10)
      Button('子组件 +1(仅本地修改)')
        .onClick(() => {
          // 修改 @Prop 变量,不会影响父组件的 count
          this.childCount++;
        })
    }
  }
}
  • 运行效果:
    • 父组件点击按钮,父组件的 count 和子组件的 childCount 都会增加。
    • 子组件点击按钮,子组件的 childCount 会增加,但父组件的 count 保持不变。
    • 父组件再次点击,子组件的值会被父组件覆盖(因为单向同步)。
  • 使用 @Link(双向):
// 父组件
@Entry
@Component
struct Parent {
  @State count: number = 10;

  build() {
    Column() {
      Text(`父组件 count: ${this.count}`)
        .fontSize(20)
        .margin(10)
      Button('父组件 +1')
        .onClick(() => {
          this.count++;
        })
      Divider()
      ChildLink({ childCount: $count })  // 注意 $ 符号
    }
    .width('100%')
    .padding(20)
  }
}

// 子组件
@Component
struct ChildLink {
  @Link childCount: number;  // 双向绑定

  build() {
    Column() {
      Text(`子组件 childCount: ${this.childCount}`)
        .fontSize(20)
        .margin(10)
      Button('子组件 +1(同步父组件)')
        .onClick(() => {
          this.childCount++;  // 修改会同步回父组件
        })
    }
  }
}
  • 运行效果:父组件或子组件任意一方点击按钮,两边的数字都会同步增加。

三、说明

① 对象类型

  • @Prop 对对象进行深拷贝,子组件内修改对象属性不会影响父组件。
  • @Link 对对象进行引用传递,子组件内修改对象属性会直接影响父组件对象。

② 嵌套深度

  • 对于深层嵌套的组件通信,@Prop 和 @Link 需要逐层传递,代码冗余。此时可考虑使用 @Provide / @Consume 或全局状态管理。

③ V2 版本变化

  • 在 ArkUI V2(@ComponentV2)中,@Prop 被 @Param 替代(单向),@Link 被 @Param + @Event 的组合替代(通过回调实现双向),不再直接使用 @Link。

四、总结

  • 使用 @Prop:单向数据流,当子组件只需要展示父组件传递的数据,并且内部修改不影响父组件时(如纯展示组件、临时编辑但最终不保存),它传递的是深拷贝。
  • 使用 @Link:双向数据流,当子组件需要修改父组件的数据,并希望父组件实时感知变化时(如表单输入、开关切换、滑块控制)。父子组件中任一方的修改都会同步给另一方,它传递的是引用,子组件中通过@Link装饰的变量可以像普通变量一样直接修改。
  • 为什么 V2 要取消 @Link,改用 @Param + @Event ?为了数据流向更清晰、更可预测。@Link 虽然方便,但子组件可以直接修改父组件的数据,在复杂应用中可能导致数据流难以追踪,V2 的方案强制子组件通过 @Event 回调来“通知”父组件修改,符合“数据向下流动,事件向上传递”的单向数据流最佳实践,让状态变化更可控。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐