第四章:组件通信
本章配套代码:
code/Chapter4(@Param 父传子 + @Event 子传父) 前置要求:已完成第三章 ArkUI 组件与布局
4.1 学习目标
- 理解组件通信的两种基本方向
- 掌握
@Param实现父传子数据传递 - 掌握
@Event实现子传父事件上报 - 学会使用
@Builder复用 UI - 了解组件生命周期
4.2 组件通信概述
组件化开发中,父子组件之间需要交换数据与事件。
┌─────────────┐
│ 父组件 │
│ │
│ @Param ◄───┼── 父传子(数据下行)
│ @Event ────┼── 子传父(事件上行)
│ │
└──────┬──────┘
│
┌──────┴──────┐
│ 子组件 │
└─────────────┘
| 装饰器 | 方向 | 说明 |
|---|---|---|
@Param | 父 → 子 | 接收父组件传入的数据,子组件内只读 |
@Event | 子 → 父 | 子组件调用回调,将数据上报给父组件 |
4.3 @Param 父传子
4.3.1 基本用法
// 子组件:接收父组件数据
@ComponentV2
struct UserCard {
@Param name: string = ''
@Param age: number = 0
build() {
Column({ space: 4 }) {
Text(this.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(`年龄: ${this.age}`)
.fontSize(14)
.fontColor('#666666')
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
}
}
// 父组件:传入数据
@Entry
@ComponentV2
struct ParentPage {
@Local userName: string = '张三'
@Local userAge: number = 25
build() {
Column() {
UserCard({
name: this.userName, // 传变量
age: this.userAge
})
Button('修改数据')
.onClick(() => {
this.userAge++ // 父数据变化,子组件自动同步
})
}
.padding(16)
}
}
4.3.2 @Param 特点
| 特性 | 说明 |
|---|---|
| 必须默认值 | 子组件中必须声明默认值(如 @Param name: string = '') |
| 单向传递 | 数据从父流向子 |
| 子内只读 | 子组件不能直接修改 @Param 变量 |
| 同步刷新 | 父组件数据变化时,子组件自动刷新 |
⚠️
@Param修饰的变量在子组件内不可赋值修改。若要修改,应通过@Event上报父组件处理。
4.4 @Event 子传父
4.4.1 基本用法
// 子组件:定义事件回调,内部触发
@ComponentV2
struct CounterChild {
@Local count: number = 0
@Event onCountChange: (count: number) => void = () => {}
build() {
Row({ space: 16 }) {
Button('-')
.onClick(() => {
if (this.count > 0) {
this.count--
this.onCountChange(this.count) // 上报父组件
}
})
Text(`${this.count}`)
.fontSize(20)
Button('+')
.onClick(() => {
this.count++
this.onCountChange(this.count) // 上报父组件
})
}
}
}
// 父组件:传入回调
@Entry
@ComponentV2
struct ParentPage {
@Local totalCount: number = 0
build() {
Column({ space: 20 }) {
Text(`总数: ${this.totalCount}`)
.fontSize(24)
CounterChild({
onCountChange: (count: number) => {
this.totalCount = count // 接收子组件数据
}
})
}
.padding(16)
}
}
4.4.2 @Event 特点
| 特性 | 说明 |
|---|---|
| 回调类型 | @Event 变量的类型必须是函数类型 |
| 默认空函数 | 必须给默认值 := () => {} |
| 事件方向 | 数据流:子 → 父 |
| 参数任意 | 回调参数个数与类型由需求决定 |
4.5 完整通信流程示例
对应 code/Chapter4 的 Index.ets,同时演示 @Param 和 @Event:
@Entry
@ComponentV2
struct Index {
@Local parentMessage: string = '何须浅碧深红色'
@Local childCount: number = 0
build() {
Column({ space: 20 }) {
Text('Chapter 4: Component Communication')
.fontSize(24)
.fontWeight(FontWeight.Bold)
// ① 父传子:@Param
Column({ space: 10 }) {
Text('Parent to Child (@Param)')
.fontSize(18)
.fontWeight(FontWeight.Bold)
ChildComponent({
message: this.parentMessage,
count: this.childCount
})
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
// ② 子传父:@Event
Column({ space: 10 }) {
Text('Child to Parent (@Event)')
.fontSize(18)
.fontWeight(FontWeight.Bold)
EventChild({
onCountChange: (count: number) => {
this.childCount = count
}
})
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#f5f5f5')
}
}
@ComponentV2
struct ChildComponent {
@Param message: string = ''
@Param count: number = 0
build() {
Column() {
Text(`Message: ${this.message}`)
.fontSize(16)
Text(`Count: ${this.count}`)
.fontSize(16)
.fontColor('#007DFF')
}
.width('100%')
.padding(12)
.backgroundColor('#f5f5f5')
.borderRadius(8)
}
}
@ComponentV2
struct EventChild {
@Local count: number = 0
@Event onCountChange: (count: number) => void = () => {}
build() {
Button(`Increment (${this.count})`)
.onClick(() => {
this.count++
this.onCountChange(this.count)
})
}
}
运行效果:
- 顶部区块:显示父组件传入的 Message 与 Count
- 底部区块:点击 "Increment" 按钮,父组件
childCount同步递增,顶部 Count 实时刷新 - 形成完整闭环:子组件通过 @Event 上报 → 父组件更新 @Local → 通过 @Param 同步回子组件
运行效果截图:



🔑 这是状态管理 V2 组件通信的核心模式,务必理解透。
4.6 @Builder 组件复用
4.6.1 基本用法
@Builder 用于定义可复用的 UI 片段(类似函数),避免重复代码:
@Entry
@ComponentV2
struct BuilderExample {
@Builder
CardBuilder(title: string, content: string) {
Column({ space: 8 }) {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(content)
.fontSize(14)
.fontColor('#666666')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
build() {
Column({ space: 16 }) {
this.CardBuilder('标题1', '内容1')
this.CardBuilder('标题2', '内容2')
this.CardBuilder('标题3', '内容3')
}
.padding(16)
}
}
4.6.2 @Builder 传参规则
- 参数按值传递:
this.CardBuilder('标题1', '内容1') - 支持传对象、数组等
@Builder内不可使用@State/@Local等装饰器(纯 UI 片段)
4.7 组件生命周期
组件有完整的生命周期回调:
@ComponentV2
struct LifecycleDemo {
aboutToAppear(): void {
console.log('组件即将挂载')
}
aboutToDisappear(): void {
console.log('组件即将销毁')
}
build() {
Text('生命周期示例')
}
}
| 回调 | 触发时机 | 用途 |
|---|---|---|
aboutToAppear | 组件创建后、首次渲染前 | 初始化数据、请求接口 |
aboutToDisappear | 组件销毁前 | 释放资源、解绑监听 |
🧠
aboutToDisappear什么时候触发?记住一个核心:它代表"从界面上被移除",而不是"被挡住"。常见触发场景:
- 返回(pop):用 Navigation
pop回上一页时,被弹出的页面销毁- 条件渲染移除:
if (false)隐藏、ForEach删除某项,对应组件树被移除- 关闭页面/退出应用:页面销毁(但强杀进程时不一定来得及执行,别在里面做关键持久化)
反之,push 跳转到下一页只是"被覆盖",触发的是
onPageHide,不触发aboutToDisappear。被覆盖 ≠ 销毁。
💡 页面级(
@Entry)还有onPageShow/onPageHide/onBackPress,后续路由章节介绍。
4.8 常见问题
Q:@Param 和 @Local 的区别?
@Param:接收父组件数据,外部初始化,内部只读@Local:组件内部私有状态,本地初始化,可自由修改
Q:子组件能否直接改 @Param?
不能。@Param 只读,应通过 @Event 回调父组件修改,父组件更新后再传回。
Q:如何实现兄弟组件通信?
通过父组件中转:父持有状态,A 兄弟用 @Event 上报,父更新后经 @Param 传给 B 兄弟。或使用后续章节的 @Provider/@Consumer/AppStorageV2。
Q:@Event 的回调参数有数量限制吗?
无严格限制,按业务需求定义。如 onChange(name: string, quantity: number)。
4.9 本章小结
| 知识点 | 说明 |
|---|---|
@Param | 父传子,单向只读 |
@Event | 子传父,回调上报 |
@Builder | UI 片段复用 |
| 生命周期 | aboutToAppear/aboutToDisappear |