跳到主要内容

第四章:组件通信

本章配套代码: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/Chapter4Index.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 同步回子组件

运行效果截图:

Chapter4 组件通信综合示例总览

父传子:@Param 展示 Message 与 Count

子传父:@Event 上报计数

🔑 这是状态管理 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 什么时候触发?记住一个核心:它代表"从界面上被移除",而不是"被挡住"。

常见触发场景:

  1. 返回(pop):用 Navigation pop 回上一页时,被弹出的页面销毁
  2. 条件渲染移除if (false) 隐藏、ForEach 删除某项,对应组件树被移除
  3. 关闭页面/退出应用:页面销毁(但强杀进程时不一定来得及执行,别在里面做关键持久化)

反之,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子传父,回调上报
@BuilderUI 片段复用
生命周期aboutToAppear/aboutToDisappear

4.10 课后练习

  1. 实现一个可增减数量的商品组件(@Param 传商品信息,@Event 上报数量变化)
  2. 设计一个评分组件(点击选择 1-5 星,@Event 上报分数)
  3. @Builder 创建一个可复用的列表项组件
  4. 将练习 1 的多个商品组合成购物车页面,父组件汇总总价

4.11 参考资料

评论

加载中…
加载中...