本节目标
· 掌握 @Component 装饰器构建自定义组件的方法,理解组件的基本结构与调用方式
· 理解自定义组件与页面的关系,区分 @Entry 与 @Component 的职责
· 掌握 @Builder 装饰器构建可复用 UI 结构的方法,理解其与 @Component 的区别与适用场景
· 熟悉 @BuilderParam 实现内容插槽(Slot)的用法
· 掌握自定义组件的生命周期回调函数:aboutToAppear、onDidBuild、aboutToDisappear
· 掌握页面生命周期回调函数:onPageShow、onPageHide、onBackPress
· 理解 @Prop、@Link、@Provide、@Consume 四种状态通信装饰器的适用场景与数据流向
· 能独立封装可复用的自定义组件,并选择合适的通信方式实现父子组件数据同步
一、自定义组件基础
1.1 自定义组件与页面的关系
在开始学习自定义组件之前,需要先理清自定义组件和页面的关系。
自定义组件:@Component 装饰的 UI 单元,可以组合多个系统组件实现 UI 的复用,可以调用组件的生命周期。
页面:即应用的 UI 页面,可以由一个或多个自定义组件组成。@Entry 装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个 @Entry。只有被 @Entry 装饰的组件才可以调用页面的生命周期。
1.2 @Component 构建自定义组件
任何一个用 @Component 修饰的 struct 都是一个自定义组件,它必须包含 build() 方法。调用时,可以像写 HTML 标签一样使用它,并通过标签属性传递参数。
一个自定义组件的基本结构如下:
@Component
struct Avatar {
@Prop url: string
@Prop size: number = 48
build() {
Image(this.url)
.width(this.size)
.height(this.size)
.borderRadius(this.size / 2)
.objectFit(ImageFit.Cover)
}
}
使用时,通过命名参数语法传递数据:
Avatar({ url: 'https://example.com/avatar.jpg', size: 64 })
关键规则:
· 自定义组件必须使用 struct 定义,并且被 @Component 装饰器修饰
· 调用时必须使用命名参数语法 { key: value },不支持位置参数
· 自定义组件可以独立导出,跨组件、跨页面复用
1.3 自定义组件的调用与嵌套
自定义组件可以嵌套使用,组合多个基础组件和自定义组件来构建复杂的 UI。@Component 是最基本的自定义封装方式,可以将已有的自定义组件和基础组件组装构成新的自定义组件,通常用于有内部成员方法和状态变量的场景。
二、@Builder 装饰器
2.1 @Builder 的定位与用法
@Builder 装饰器用于封装可复用的 UI 结构。相比于 @Component 装饰器,@Builder 装饰器更加轻量级,可以将纯粹的 UI 结构封装成方法,在各个自定义组件的 build 方法中进行调用,达成 UI 结构复用的目的。相当于将 @Component 里面的 build 部分单独抽出来,而不包含状态变量本身。
@Builder
function buildCard(title: string, content: string) {
Column() {
Text(title).fontSize(18).fontWeight(FontWeight.Bold)
Text(content).fontSize(14).fontColor(Color.Gray)
}
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
}
在自定义组件中调用:
@Component
struct CardList {
build() {
Column() {
buildCard('标题一', '内容一')
buildCard('标题二', '内容二')
}
}
}
2.2 @Builder 的参数传递规则
@Builder 装饰的函数支持参数传递,调用时默认按值传递。按值传递时状态变量的改变不会触发 UI 刷新,所以当使用状态变量的时候,推荐使用按引用传递或按回调传递。
参数传递需遵守以下规则:
· 参数的类型必须与参数声明的类型一致,不允许 undefined、null 和返回 undefined、null 的表达式
· 在自定义构建函数内部,不允许改变参数值
· 只有传入一个参数,且参数需要直接传入对象字面量时,才会按引用传递该参数
2.3 @BuilderParam 实现内容插槽
当一个组件不仅要接收数据,还需要允许外部嵌入自定义内容时,就需要用到“插槽”机制。ArkTS 使用 @Builder 定义可插入的构建函数,并通过 @BuilderParam 在组件内占位。
@Component
struct Card {
@BuilderParam headerBuilder: () => void
@BuilderParam contentBuilder: () => void
build() {
Column() {
this.headerBuilder()
Divider()
this.contentBuilder()
}
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
}
}
使用时,外部通过尾随闭包传递内容:
Card() {
Text('标题').fontSize(18).fontWeight(FontWeight.Bold)
} {
Text('这是主体内容区域')
}
注意:提供多个插槽时,外部闭包的顺序必须与组件内 @BuilderParam 的声明顺序一致。
2.4 @Component 与 @Builder 的区别
维度 @Component 自定义组件 @Builder 构建函数
状态管理 支持 @State、@Prop、@Link 等 不支持状态管理
生命周期 有完整的生命周期钩子 没有生命周期
事件处理 可以定义事件处理方法 需要通过参数传递回调
参数传递 通过组件属性(Props) 通过函数参数
独立导出 可以独立导出 全局 Builder 可以
适用场景 复杂逻辑和交互 简单 UI 组合,轻量级
核心结论:@Component 是最基本的、功能最完善的自定义封装方式;@Builder 是 @Component 的补充,在只是简单组合组件的情况下比 @Component 更便捷。@builder 能实现的,@component 都能实现,但 @builder 更轻量,性能更好。
三、自定义组件生命周期
自定义组件的生命周期回调函数用于通知开发者该自定义组件的生命周期,这些回调函数是私有的,在运行时由开发框架在特定的时间进行调用,不能从应用程序中手动调用这些回调函数。
3.1 组件生命周期
一般用 @Component 装饰的自定义组件的生命周期,提供以下接口:
aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其 build() 函数之前执行。允许在该函数中改变状态变量,更改将在后续执行 build() 函数中生效。
onDidBuild:组件 build() 函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际 UI 的功能。不建议在 onDidBuild 函数中更改状态变量、使用 animateTo 等功能,这可能会导致不稳定的 UI 表现。
aboutToDisappear:在自定义组件析构销毁之前执行。不允许在 aboutToDisappear 函数中改变状态变量,特别是 @Link 变量的修改可能会导致应用程序行为不稳定。
3.2 页面生命周期
页面生命周期,即被 @Entry 装饰的组件生命周期,提供以下接口:
onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
onBackPress:当用户点击返回按钮时触发。
3.3 生命周期执行流程
组件创建与渲染流程:
- 自定义组件的实例由 ArkUI 框架创建
- 初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序
- 如果定义了 aboutToAppear,则执行该方法
- 首次渲染时执行 build() 方法渲染系统组件
- 如果定义了 onDidBuild,则执行该方法
组件销毁流程:
- 触发 aboutToDisappear
- 解除与父组件的所有绑定
- 被 JS 虚拟机回收
关键区别:页面返回栈中时可能只是隐藏,并不会重新创建,因此返回页面时不要依赖 aboutToAppear 刷新数据,应在 onPageShow 中处理。
四、组件间通信
4.1 @Prop:父组件向子组件单向传值
@Prop 用于父组件向子组件传递数据。子组件可以读取,也可以在自身作用域内修改,但这种修改不会反向改变父组件中的源数据。
@Component
struct Child {
@Prop count: number
build() {
Text(`子组件计数:${this.count}`)
}
}
@Entry
@Component
struct Parent {
@State count: number = 0
build() {
Column() {
Child({ count: this.count })
Button('增加').onClick(() => { this.count++ })
}
}
}
@Prop 实现父组件向子组件单向传值,子组件修改不会影响父组件。@Prop 装饰的变量和 @State 以及其他装饰器同步时双方的类型必须相同。
4.2 @Link:父子组件双向同步
@Link 在父子组件间建立双向数据绑定。子组件中被 @Link 装饰的变量与其父组件中对应的数据源建立双向数据绑定。传递时必须使用 $ 语法糖表示传引用而非值拷贝,子组件对 @Link 变量的修改会同步回父组件,触发双方 UI 刷新。
@Component
struct Child {
@Link count: number
build() {
Row() {
Text(`子组件:${this.count}`)
Button('+1').onClick(() => { this.count++ })
}
}
}
@Entry
@Component
struct Parent {
@State count: number = 0
build() {
Column() {
Text(`父组件:${this.count}`)
Child({ count: $count }) // 使用 $ 语法传引用
}
}
}
4.3 @Provide / @Consume:跨层级双向同步
@Provide 和 @Consume 配套使用,用于实现跨组件层级的双向同步,适用于需要在多层嵌套组件间共享状态的场景,能够避免逐层传递的繁琐,简化组件间的通信逻辑。
@Component
struct GrandChild {
@Consume theme: string
build() {
Text(`当前主题:${this.theme}`)
}
}
@Component
struct Child {
build() {
GrandChild() // 无需传递参数
}
}
@Entry
@Component
struct Parent {
@Provide theme: string = 'dark'
build() {
Column() {
Child()
Button('切换主题').onClick(() => {
this.theme = this.theme === 'dark' ? 'light' : 'dark'
})
}
}
}
@Provide 装饰的变量作为数据源,@Consume 装饰的变量在子孙组件中消费数据,摆脱了参数传递机制的束缚,实现跨层级传递。
4.4 四种通信装饰器的选型建议
对于父子组件关联的场景,@Provide + @Consume 开销要大于 @State + @Prop / @Link,因此在该场景下推荐使用 @State + @Prop / @Link 的组合。对于跨越多层的状态变量传递,使用 @Provide + @Consume 方案更为合理。
装饰器 数据流向 适用场景
@Prop 父→子单向 子组件只展示父组件数据,不需要回传
@Link 父子双向 父子组件需要同步修改同一数据
@Provide/@Consume 跨层级双向 多层嵌套组件间共享状态
五、多元化习题
习题 1(判断题)
题目:在 ArkTS 中,一个页面文件中可以使用多个 @Entry 装饰不同自定义组件。
答案:错误
解读:@Entry 装饰的自定义组件为页面的入口组件,一个页面有且仅能有一个 @Entry。@Component 可以有多个,但 @Entry 只能有一个。
习题 2(单选题)
题目:以下关于 @Builder 和 @Component 的说法,正确的是( )
A. @Builder 支持 @State 状态管理
B. @Builder 具有完整的生命周期钩子
C. @Component 功能更完善,@Builder 更轻量
D. @Builder 不能独立导出使用
答案:C
解读:@Builder 不支持状态管理和生命周期,但更轻量级。@Component 是最基本的自定义封装方式,功能最完善。@builder 能实现的,@component 都能实现,但 @builder 在简单 UI 组合场景下更便捷。全局 Builder 可以独立导出。
习题 3(多选题)
题目:以下哪些是 @Component 装饰的自定义组件特有的生命周期回调?(多选)
A. aboutToAppear
B. onPageShow
C. onDidBuild
D. onBackPress
答案:A、C
解读:aboutToAppear 和 onDidBuild 是所有 @Component 组件都有的组件级生命周期。onPageShow 和 onBackPress 是页面级生命周期,仅 @Entry 装饰的组件才可以调用。
习题 4(代码填空题)
题目:请补全以下代码,实现父组件向子组件传递数据,并在子组件中修改后不影响父组件。
@Component
struct Child {
// 在此处填写装饰器
______________ count: number
build() {
Text(`子组件:${this.count}`)
}
}
@Entry
@Component
struct Parent {
@State count: number = 10
build() {
Column() {
Child({ count: this.count })
}
}
}
答案:@Prop
解读:@Prop 用于父组件向子组件单向传递数据,子组件可以读取和修改,但修改不会反向改变父组件中的源数据。如果是双向同步则应使用 @Link。
习题 5(代码改错题)
题目:以下代码存在错误,请指出问题并修正。
@Component
struct Child {
@Link count: number
build() {
Text(`${this.count}`)
}
}
@Entry
@Component
struct Parent {
@State count: number = 0
build() {
Column() {
Child({ count: this.count })
}
}
}
答案:传递 @Link 变量时需要使用 $ 语法糖传递引用,而非直接传值。修正如下:
Child({ count: $count })
解读:@Link 建立的是父子组件间的双向数据绑定,传递时必须使用 $ 语法糖表示传引用而非值拷贝。如果直接写 this.count,传递的是值的副本,无法建立双向绑定。
习题 6(简答题)
题目:简述 @Prop、@Link、@Provide / @Consume 三种通信方式的区别及各自的适用场景。
答案:@Prop 实现父组件向子组件的单向传值,子组件修改不会影响父组件,适用于子组件只展示父组件数据、不需要回传的场景。@Link 建立父子组件间的双向数据绑定,传递时使用 $ 语法糖传引用,子组件的修改会同步回父组件,适用于父子组件需要同步修改同一数据的场景。@Provide / @Consume 配套使用,实现跨组件层级的双向同步,摆脱参数传递机制的束缚,适用于多层嵌套组件间共享状态的场景。
解读:选择通信方式时需要综合考虑数据流向和组件层级。父子组件关联的场景下推荐 @State + @Prop / @Link;跨越多层传递状态时使用 @Provide + @Consume 更为合理。
六、本节知识点总结
自定义组件与页面
@Component 装饰自定义组件,@Entry 装饰页面入口,一个页面有且仅有一个 @Entry。自定义组件可以跨组件、跨页面复用。
@Builder 构建函数
轻量级 UI 复用机制,支持参数传递(按值/按引用/按回调),不支持状态管理和生命周期。@BuilderParam 实现内容插槽,外部通过尾随闭包嵌入自定义内容。
@Component 与 @Builder 的区别
@Component 功能完善,支持状态管理和生命周期,适合复杂交互;@Builder 轻量灵活,适合简单 UI 组合。@builder 能实现的,@component 都能实现。
组件生命周期
aboutToAppear 在 build() 前执行,onDidBuild 在 build() 后执行,aboutToDisappear 在组件销毁前执行。生命周期回调由框架自动调用,不能手动触发。
页面生命周期
onPageShow、onPageHide、onBackPress 仅 @Entry 装饰的组件可用。页面返回栈中时可能只是隐藏而非重新创建,数据刷新应在 onPageShow 中处理。
组件通信
@Prop 实现父→子单向传值;@Link 实现父子双向绑定,传递需使用 $ 语法;@Provide / @Consume 实现跨层级双向同步,无需逐层传参。
下节预告
第4课将深入讲解 ArkUI 的高级布局技术,包括 Flex 弹性布局、Grid 网格布局、Stack 层叠布局以及 List 列表布局的使用方法与性能优化技巧。

1357

被折叠的 条评论
为什么被折叠?



