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)
}
}
三、总结:组件通信的核心要点
✅ 核心知识点回顾
- @Prop单向传递:数据从父组件流向子组件,子组件修改不影响父组件
- @Link双向绑定:父子组件数据保持同步,任何修改都会双向更新
- **rawParam包装∗∗:@Link传递引用类型时必须使用rawParam
- 引用传递特性:对象和数组传递的是引用,子组件修改会影响父组件
- 类型一致性:装饰器变量必须与传递的数据类型相同
⚠️ 常见问题与解决方案
- @Link不生效:检查是否使用了$rawParam包装引用类型
- @Prop无法修改:确保不是传递的const常量
- 对象修改影响父组件:使用深拷贝避免引用传递的影响
- 类型错误:确保父子组件的数据类型完全一致
🎯 最佳实践建议
- 合理选择装饰器:根据业务需求选择@Prop或@Link,避免过度使用双向绑定
- 避免深层次传递:不要超过3层组件传递,考虑使用AppStorage或EventBus
- 使用接口类型:为复杂数据类型定义接口,提高代码可读性
- 性能优化:对于频繁更新的数据,使用@Link配合@Watch进行监听
下一步预告:在第八篇中,我们将学习AppStorage全局状态管理,掌握如何实现跨组件、跨页面的状态共享,解决深层组件通信的痛点。

浙公网安备 33010602011771号