Harmony之路:组件间对话——@Prop与@Link通信机制

一、引入:组件通信的必要性

在组件化开发中,父子组件之间的数据传递是构建复杂应用的基础。HarmonyOS提供了多种装饰器来实现组件间的数据通信,其中@Prop@Link是最常用的两种单向和双向数据绑定方式。理解它们的区别和使用场景,能够帮助我们设计出更清晰、更易维护的组件结构。

二、讲解:@Prop与@Link的核心用法

1. @Prop装饰器:单向数据传递

@Prop装饰的变量允许从父组件单向传递数据到子组件,子组件可以修改该变量的值,但不会同步回父组件。

基础用法示例:

// 子组件:接收父组件传递的数据
@Component
struct ChildComponent {
  @Prop message: string = '';  // 从父组件接收数据

  build() {
    Column() {
      Text(`子组件接收: ${this.message}`)
        .fontSize(18)
      
      Button('修改数据')
        .onClick(() => {
          this.message = '子组件修改了数据';
          console.log('子组件数据:', this.message);
          // 这里的修改不会影响父组件
        })
    }
    .padding(15)
    .border({ width: 1, color: Color.Blue })
  }
}

// 父组件:传递数据给子组件
@Entry
@Component
struct ParentComponent {
  @State parentMessage: string = '父组件初始数据';

  build() {
    Column({ space: 20 }) {
      Text(`父组件数据: ${this.parentMessage}`)
        .fontSize(20)
      
      Button('修改父组件数据')
        .onClick(() => {
          this.parentMessage = '父组件更新了数据';
        })
      
      // 传递数据给子组件
      ChildComponent({ message: this.parentMessage })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Prop的核心特性:

  • 单向传递:数据从父组件流向子组件
  • 可修改:子组件可以修改@Prop变量的值
  • 不反向同步:子组件的修改不会影响父组件的源数据
  • 类型一致:必须与父组件传递的数据类型相同

2. @Link装饰器:双向数据绑定

@Link装饰的变量实现父子组件间的双向数据同步,任何一方的修改都会同步到另一方。

基础用法示例:

// 子组件:双向绑定数据
@Component
struct ChildLinkComponent {
  @Link @Watch('onMessageChange') message: string;  // 双向绑定

  onMessageChange() {
    console.log('子组件数据变化:', this.message);
  }

  build() {
    Column() {
      Text(`子组件数据: ${this.message}`)
        .fontSize(18)
      
      Button('子组件修改')
        .onClick(() => {
          this.message = '子组件修改了数据';
        })
    }
    .padding(15)
    .border({ width: 1, color: Color.Red })
  }
}

// 父组件:与子组件双向绑定
@Entry
@Component
struct ParentLinkComponent {
  @State parentMessage: string = '父组件初始数据';

  build() {
    Column({ space: 20 }) {
      Text(`父组件数据: ${this.parentMessage}`)
        .fontSize(20)
      
      Button('父组件修改')
        .onClick(() => {
          this.parentMessage = '父组件更新了数据';
        })
      
      // 双向绑定数据
      ChildLinkComponent({ message: $rawParam(this.parentMessage) })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Link的核心特性:

  • 双向同步:父子组件的数据保持同步
  • **必须使用rawParam∗∗:传递引用类型时需要使用rawParam包装
  • 引用传递:传递的是数据的引用,而非拷贝
  • 类型一致:必须与父组件传递的数据类型相同

3. @Prop与@Link的区别对比

特性 @Prop @Link
数据流向 单向(父→子) 双向(父↔子)
子组件修改 可以修改,不影响父组件 修改会同步到父组件
传递方式 值传递 引用传递
初始化 必须提供默认值 不需要默认值
使用场景 子组件需要独立副本 父子组件需要数据同步

4. 复杂数据类型传递

对于对象和数组等复杂数据类型,@Prop和@Link的传递方式有所不同。

对象类型传递示例:

class UserInfo {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Component
struct UserCard {
  @Prop user: UserInfo;  // 对象类型@Prop

  build() {
    Column() {
      Text(`姓名: ${this.user.name}`)
      Text(`年龄: ${this.user.age}`)
      
      Button('修改年龄')
        .onClick(() => {
          this.user.age += 1;  // 修改对象属性
          console.log('子组件年龄:', this.user.age);
        })
    }
    .padding(15)
    .border({ width: 1, color: Color.Green })
  }
}

@Entry
@Component
struct UserManager {
  @State user: UserInfo = new UserInfo('张三', 25);

  build() {
    Column({ space: 20 }) {
      Text(`父组件年龄: ${this.user.age}`)
        .fontSize(20)
      
      Button('父组件修改年龄')
        .onClick(() => {
          this.user.age += 1;
        })
      
      // 传递对象给子组件
      UserCard({ user: this.user })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

重要提醒:

  • @Prop传递对象:子组件修改对象属性会影响父组件(因为传递的是引用)
  • @Link传递对象:必须使用$rawParam包装
  • 不可变更新:如果需要避免子组件修改影响父组件,应该传递对象的深拷贝

5. 数组类型传递

数组的传递方式与对象类似,需要注意引用传递的特性。

数组传递示例:

@Component
struct TodoList {
  @Prop todos: string[];  // 数组类型@Prop

  build() {
    Column() {
      ForEach(this.todos, (item: string, index: number) => {
        Text(`${index + 1}. ${item}`)
      })
      
      Button('添加任务')
        .onClick(() => {
          this.todos.push('新任务');  // 修改数组
          console.log('子组件任务数:', this.todos.length);
        })
    }
    .padding(15)
    .border({ width: 1, color: Color.Orange })
  }
}

@Entry
@Component
struct TodoManager {
  @State todos: string[] = ['学习ArkTS', '掌握@Prop', '完成项目'];

  build() {
    Column({ space: 20 }) {
      Text(`父组件任务数: ${this.todos.length}`)
        .fontSize(20)
      
      Button('父组件添加任务')
        .onClick(() => {
          this.todos.push('父组件添加的任务');
        })
      
      // 传递数组给子组件
      TodoList({ todos: this.todos })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

6. 实际应用场景

场景1:表单输入组件(@Link双向绑定)

@Component
struct InputField {
  @Link value: string;  // 双向绑定输入值
  @Prop placeholder: string = '';  // 单向传递占位符

  build() {
    TextInput({ text: this.value, placeholder: this.placeholder })
      .width('80%')
      .height(40)
      .onChange((value: string) => {
        this.value = value;  // 自动同步到父组件
      })
  }
}

@Entry
@Component
struct FormPage {
  @State username: string = '';
  @State password: string = '';

  build() {
    Column({ space: 20 }) {
      InputField({ value: $rawParam(this.username), placeholder: '请输入用户名' })
      InputField({ value: $rawParam(this.password), placeholder: '请输入密码' })
      
      Text(`用户名: ${this.username}`)
      Text(`密码: ${this.password}`)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

场景2:展示组件(@Prop单向传递)

@Component
struct ProductCard {
  @Prop product: Product;  // 只读展示,不需要双向绑定
  @Prop onBuy: () => void;  // 传递回调函数

  build() {
    Column({ space: 10 }) {
      Image(this.product.image)
        .width(120)
        .height(120)
      Text(this.product.name)
        .fontSize(16)
      Text(`¥${this.product.price}`)
        .fontSize(14)
        .fontColor(Color.Red)
      Button('购买')
        .onClick(() => {
          this.onBuy?.();  // 调用父组件传递的回调
        })
    }
    .padding(15)
    .backgroundColor(Color.White)
    .borderRadius(8)
  }
}

三、总结:组件通信的核心要点

✅ 核心知识点回顾

  1. @Prop单向传递:数据从父组件流向子组件,子组件修改不影响父组件
  2. @Link双向绑定:父子组件数据保持同步,任何修改都会双向更新
  3. **rawParam包装∗∗:@Link传递引用类型时必须使用rawParam
  4. 引用传递特性:对象和数组传递的是引用,子组件修改会影响父组件
  5. 类型一致性:装饰器变量必须与传递的数据类型相同

⚠️ 常见问题与解决方案

  1. @Link不生效:检查是否使用了$rawParam包装引用类型
  2. @Prop无法修改:确保不是传递的const常量
  3. 对象修改影响父组件:使用深拷贝避免引用传递的影响
  4. 类型错误:确保父子组件的数据类型完全一致

🎯 最佳实践建议

  1. 合理选择装饰器:根据业务需求选择@Prop或@Link,避免过度使用双向绑定
  2. 避免深层次传递:不要超过3层组件传递,考虑使用AppStorage或EventBus
  3. 使用接口类型:为复杂数据类型定义接口,提高代码可读性
  4. 性能优化:对于频繁更新的数据,使用@Link配合@Watch进行监听

下一步预告:在第八篇中,我们将学习AppStorage全局状态管理,掌握如何实现跨组件、跨页面的状态共享,解决深层组件通信的痛点。

posted @ 2025-12-23 23:02  蓝莓Reimay  阅读(0)  评论(0)    收藏  举报