Appearance
组件参数传递
ArkTS 中组件间数据流动的核心机制:@Prop、@Link、@BuilderParam、@Provide/@Consume。
1. 组件间数据流动的四种方式
父 → 子
├── @Prop : 单向拷贝(父 → 子)
├── @Link : 双向引用(父 ↔ 子)
└── @BuilderParam : 父向子传递 UI 结构
跨树(爷 → 孙)
├── @Provide : 祖先提供
└── @Consume : 后代消费2. @Prop — 单向数据流
2.1 基本用法
typescript
// 子组件
@Component
struct UserProfile {
@Prop userName: string = '未知'
@Prop userAge: number = 0
@Prop isVip: boolean = false
build() {
Row() {
Text(this.userName)
.fontSize(20fp)
.fontWeight(FontWeight.Bold)
Text(this.isVip ? ' VIP' : '')
.fontColor(Color.Red)
Text(` (${this.userAge}岁)`)
.fontColor(Color.Gray)
}
}
}
// 父组件
@Entry
@Component
struct Index {
@State currentUser: {
name: string
age: number
isVip: boolean
} = {
name: '小明',
age: 25,
isVip: true
}
build() {
Column() {
UserProfile({
userName: this.currentUser.name,
userAge: this.currentUser.age,
isVip: this.currentUser.isVip
})
Button('修改名字')
.onClick(() => {
// 修改父组件状态 → 子组件自动更新
this.currentUser = {
...this.currentUser,
name: '小红'
}
})
}
}
}2.2 @Prop 的限制
| 特性 | 说明 |
|---|---|
| 方向 | 单向(父 → 子) |
| 传递方式 | 深拷贝(复杂对象性能差!) |
| 子组件修改 | ❌ 不允许修改 @Prop 属性 |
| 性能 | 简单类型好,复杂对象差 |
⚠️ @Prop 传递复杂对象时是深拷贝,如果对象很大,拷贝耗时且占用内存。应改用 @Link 或 @ObjectLink。
3. @Link — 双向数据流
3.1 基本用法
typescript
// 子组件
@Component
struct CounterChild {
@Link count: number // 双向绑定
build() {
Row() {
Button('-')
.onClick(() => {
this.count-- // 修改子 → 自动同步到父
})
Text(`${this.count}`)
.fontSize(24fp)
.width(60)
.textAlign(TextAlign.Center)
Button('+')
.onClick(() => {
this.count++ // 修改子 → 自动同步到父
})
}
}
}
// 父组件
@Entry
@Component
struct Index {
@State count: number = 0
build() {
Column() {
Text(`父组件计数: ${this.count}`)
CounterChild({ count: this.count })
}
}
}3.2 @Link 的使用规则
typescript
// ✅ 父组件的变量必须是 @State 或 @Link
@State count: number = 0
@Link childName: string // 从更外层 @Link 过来的也可以
// ✅ 传递方式
ChildComponent({ name: this.count })
// ❌ 不能直接传常量
ChildComponent({ name: 42 }) // 编译错误3.3 @Link vs @Prop 对比
| 特性 | @Prop | @Link |
|---|---|---|
| 数据流 | 单向(父 → 子) | 双向(父 ↔ 子) |
| 传递方式 | 深拷贝 | 引用 |
| 子组件可修改 | ❌ | ✅ |
| 性能(复杂对象) | 差(深拷贝) | 好(引用) |
| 适用场景 | 数据只读、简单类型 | 需要双向同步、复杂对象 |
4. @BuilderParam — 传递 UI 结构
4.1 基本用法
typescript
// 子组件:定义一个占位符
@Component
struct Card {
@BuilderParam contentBuilder: () => void
build() {
Column() {
// 渲染父组件传入的 UI
this.contentBuilder()
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: Color.FromRGBA(0, 0, 0, 0.1) })
}
}
// 父组件:传入自定义 UI
@Entry
@Component
struct Index {
build() {
Column() {
Card() {
() => {
// 父组件自定义内容
Row() {
Text('标题').fontSize(20fp).fontWeight(FontWeight.Bold)
Blank()
Text('详情 >').fontColor(Color.Blue)
}
Text('这是一段卡片内容')
.fontSize(16fp)
.margin({ top: 8 })
}
}
}
}
}4.2 带参数的 @BuilderParam
typescript
// 子组件
@Component
struct ListCard {
@BuilderParam titleBuilder: () => void
@BuilderParam bodyBuilder: (item: string) => void
@State items: string[] = ['数据1', '数据2', '数据3']
build() {
Column() {
this.titleBuilder()
ForEach(this.items, (item: string) => {
this.bodyBuilder(item)
})
}
}
}
// 父组件
@Entry
@Component
struct Index {
build() {
ListCard({
titleBuilder: () => Text('列表标题').fontSize(20fp),
bodyBuilder: (item: string) => Text(item).fontSize(16fp)
})
}
}4.3 @BuilderParam 的类比
ArkTS @BuilderParam = Vue slot = React render props5. @Provide / @Consume — 跨树共享
5.1 基本概念
UIAbility / AppStorage(应用级)
↓ @Provide 提供
页面 A(父组件)
↓ @Provide(向下传递)
组件 B(子组件)
↓ @Consume 消费
组件 C(孙子组件)← 不需要组件 B 转发5.2 基本用法
typescript
// 祖父组件:提供数据
@Entry
@Component
struct App {
@Provide theme: Theme = Theme.Light
@Provide userInfo: UserInfo = { name: '小明', age: 25 }
build() {
Column() {
ChildPage()
}
}
}
// 中间组件:不需要做任何事(不用转发)
@Component
struct ChildPage {
build() {
Column() {
GrandChild()
}
}
}
// 孙子组件:消费数据
@Component
struct GrandChild {
@Consume theme: Theme
@Consume userInfo: UserInfo
build() {
Text(`用户: ${this.userInfo.name}, 主题: ${this.theme}`)
}
}5.3 @Provide / @Consume 的限制
| 限制 | 说明 |
|---|---|
| 必须成对出现 | 有 @Provide 必须有 @Consume |
| 类型必须一致 | @Provide 和 @Consume 的类型完全匹配 |
| 不可为常量 | @Provide 的变量必须是 @State |
| 值变化时同步 | @Consume 会自动跟随 @Provide 更新 |
6. @ObjectLink — 嵌套对象绑定
6.1 问题背景
typescript
// @State 只能观察数组的增删和对象的第一层属性
@Component
struct Index {
@State user: User = { name: '小明', address: { city: '北京' } }
// ✅ @State 能检测到 name 变化
changeName() {
this.user.name = '小红' // ✅ 触发刷新
}
// ❌ @State 不能检测到嵌套对象属性变化
changeCity() {
this.user.address.city = '上海' // ❌ 不会触发刷新!
}
}6.2 使用 @Observed + @ObjectLink
typescript
// 被观察的类
@Observed
class Address {
city: string = '北京'
street: string = '长安街'
}
@Observed
class User {
name: string = '小明'
address: Address = new Address()
}
// 父组件
@Entry
@Component
struct Index {
@State user: User = new User()
build() {
Column() {
// 传递完整对象引用
UserCard({ user: this.user })
Button('修改城市')
.onClick(() => {
this.user.address.city = '上海' // ✅ 现在可以触发刷新!
})
}
}
}
// 子组件
@Component
struct UserCard {
@ObjectLink user: User // 深度绑定
build() {
Text(`城市: ${this.user.address.city}`)
.fontSize(16fp)
}
}7. 状态管理方案选择决策树
需要传递数据吗?
├─ 是 → 父→子单向?
│ ├─ 是 → @Prop(简单类型) / @Link(复杂对象)
│ └─ 否 → 需要父↔子双向?
│ ├─ 是 → @Link
│ └─ 否 → 爷孙跨树?
│ ├─ 是 → @Provide/@Consume
│ └─ 否 → 应用全局?
│ ├─ 是 → AppStorage
│ └─ 否 → @State 组件内
└─ 否 → 不需要传参8. 面试高频考点
Q1: @Prop 和 @Link 的区别?
回答:@Prop 是单向拷贝(父→子),适合只读数据;@Link 是双向引用(父↔子),适合需要双向同步的数据。复杂对象建议用 @Link(@Prop 深拷贝性能差)。
Q2: @BuilderParam 的作用?类比什么?
回答:让父组件向子组件传递一段 UI 结构。类似 Vue 的 slot 或 React 的 render props。
Q3: @Provide/@Consume 的使用场景?
回答:跨组件层级通信(爷孙通信),无需中间组件转发。爷爷 @Provide,孙子 @Consume 即可。
🐱 小猫提示:参数传递记住口诀——"单向用 @Prop,双向用 @Link,传 UI 用 @BuilderParam,跨树用 @Provide/@Consume"。面试核心考点!