Skip to content

组件参数传递

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.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 })
        }
    }
}
typescript
// ✅ 父组件的变量必须是 @State 或 @Link
@State count: number = 0
@Link childName: string  // 从更外层 @Link 过来的也可以

// ✅ 传递方式
ChildComponent({ name: this.count })

// ❌ 不能直接传常量
ChildComponent({ name: 42 })  // 编译错误
特性@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 props

5. @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.1 问题背景

typescript
// @State 只能观察数组的增删和对象的第一层属性
@Component
struct Index {
    @State user: User = { name: '小明', address: { city: '北京' } }

    // ✅ @State 能检测到 name 变化
    changeName() {
        this.user.name = '小红'  // ✅ 触发刷新
    }

    // ❌ @State 不能检测到嵌套对象属性变化
    changeCity() {
        this.user.address.city = '上海'  // ❌ 不会触发刷新!
    }
}
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. 面试高频考点

回答:@Prop 是单向拷贝(父→子),适合只读数据;@Link 是双向引用(父↔子),适合需要双向同步的数据。复杂对象建议用 @Link(@Prop 深拷贝性能差)。

Q2: @BuilderParam 的作用?类比什么?

回答:让父组件向子组件传递一段 UI 结构。类似 Vue 的 slot 或 React 的 render props。

Q3: @Provide/@Consume 的使用场景?

回答:跨组件层级通信(爷孙通信),无需中间组件转发。爷爷 @Provide,孙子 @Consume 即可。


🐱 小猫提示:参数传递记住口诀——"单向用 @Prop,双向用 @Link,传 UI 用 @BuilderParam,跨树用 @Provide/@Consume"。面试核心考点!