【HarmonyOS】ArkUI - 自定义组件和结构重用
文章目录
- 一、自定义组件
- 1、使用场景
- 2、基本结构
- (1)struct
- (2)@Component
- (3)build()函数
- (4)@Entry
- 3、生命周期
- (1)生命周期函数
- (2)重新渲染
- 二、组件扩展
- 1、@Builder
- (1)概念
- (2)分类
- (3)参数传递规则
- (3)常见问题
- 2、@LocalBuilder(维持组件关系)
- (1)概念
- (2)和局部@Builder使用区别
- (3)参数传递规则
- 3、@BuilderParam(引用@Builder)
- (1)概念
- (2)多个 @BuilderParam 参数
- 三、结构重用
- 1、`@Extend` 装饰器
- 2、`@Styles` 装饰器
- 关键点
- 四、装饰器对比
- 五、使用场景
- 六、注意事项
一、自定义组件
在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在鸿蒙应用开发中,@Component
是声明自定义组件的核心装饰器,用于将一个类标记为可复用的 UI 组件。自定义组件是构建鸿蒙 UI 界面的基础,支持组合、嵌套和复用,极大提升了开发效率。
一个完整的自定义组件需包含以下部分:
@Component
装饰器:标记类为自定义组件。build()
方法:定义组件的 UI 结构,必须实现。- 成员变量:存储组件状态(如
@State
装饰的响应式数据)。 - 生命周期方法(可选):如
aboutToAppear()
(组件即将显示)、aboutToDisappear()
(组件即将销毁)。
1、使用场景
- 自定义组件
@Component struct HelloComponent
组合系统组件。 - 在其他自定义组件的build()函数中多次创建HelloComponent,以实现自定义组件的重用。
- 通过状态变量的改变,来驱动UI的刷新。
2、基本结构
(1)struct
自定义组件基于struct实现,struct + 自定义组件名 + {...}
的组合构成自定义组件,不能有继承关系。
(2)@Component
@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。
(3)build()函数
build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
所有在build()函数中声明的语句统称为UI描述,UI描述需要遵循以下规则:
- @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点。
- @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。
(4)@Entry
@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,仅允许存在一个由@Entry装饰的自定义组件作为页面的入口。二、应用模型。
3、生命周期
(1)生命周期函数
-
aboutToAppear?(): void
aboutToAppear函数在创建自定义组件的新实例后,在执行其build()函数之前执行。允许在aboutToAppear函数中改变状态变量,更改将在后续执行build()函数中生效。
-
onDidBuild?(): void
onDidBuild函数在执行自定义组件的build()函数之后执行,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
onPageShow()
(仅页面级组件)
- 触发时机:组件所在页面被显示时(如从后台切换到前台)。
- 作用:可以在这里执行页面显示后的逻辑(如刷新数据)。
onPageHide()
(仅页面级组件)
- 触发时机:组件所在页面被隐藏时(如切换到其他页面)。
- 作用:可以暂停耗时操作(如动画、定时器)。
-
aboutToDisappear?(): void
aboutToDisappear函数在自定义组件析构销毁时执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
(2)重新渲染
当触发事件(比如点击)改变状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:
- 框架观察到变化,启动重新渲染。
- 根据框架记录的状态变量和组件的映射关系,仅刷新发生变化的状态变量所关联的组件,实现最小化更新。
二、组件扩展
1、@Builder
@Builder装饰器装饰器专为构建模块化、可复用的UI结构而设计,其内部禁止定义状态变量和调用组件生命周期方法,仅支持通过参数与调用方进行数据交互。
(1)概念
- 作用
定义一个构建器函数,用于生成可复用的 UI 片段。类似于组件,但更轻量,无独立生命周期。
- 用法
- 用
@Builder
装饰函数。 - 函数内部返回 UI 组件(如
Column
、Text
等)。 - 可通过参数接收外部数据。
- 用
- 示例
@Builder
function MyButton({ text, onClick }: { text: string; onClick: () => void }) {Button(text).onClick(onClick).width('100%').backgroundColor('#007DFF').fontColor('#FFFFFF')
}@Entry
@Component
struct MainPage {build() {Column() {MyButton({ text: "点击我", onClick: () => console.log("按钮被点击") })}}
}
(2)分类
- 私有自定义构建函数(@component组件内部)
@BuildershowTextBuilder() {// @Builder装饰此函数,使其能以链式调用的方式配置并构建Text组件Text('Hello World').fontSize(30).fontWeight(FontWeight.Bold)}
- 在自定义组件中,this指代当前所属组件,组件的状态变量可在自定义构建函数内访问。建议通过this访问组件的状态变量,而不是通过参数传递。
- 全局自定义构建函数(@component组件外部)
@Builder
function showTextBuilder() {Text('Hello World').fontSize(30).fontWeight(FontWeight.Bold)
}
- 如果不涉及组件状态变化,建议使用全局的自定义构建函数。
- 全局自定义构建函数允许在build函数和其他自定义构建函数中调用。
(3)参数传递规则
- 按值传递参数
调用@Builder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@Builder函数内的UI刷新。
@State label: string = 'Hello';
Column() {overBuilder(this.label)}
- 按引用传递参数
按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder函数内的UI刷新。
@State label: string = 'Hello';
Column() {overBuilder({ paramA1: this.label })Button('Click me').onClick(() => {this.label = 'ArkUI';})}
(3)常见问题
- @Builder存在两个或者两个以上参数,即使通过对象字面量形式传递,值的改变也不会触发UI刷新。因此,@Builder只接受一个参数,值的改变会引起UI的刷新。
- 在parentBuilder函数中创建自定义组件HelloComponent,传递参数为class对象并修改对象内的值时,UI不会触发刷新功能。传递参数为对象字面量形式并修改对象内的值时,UI触发刷新功能。
2、@LocalBuilder(维持组件关系)
(1)概念
- 作用
定义一个局部构建器,仅在当前组件内部可见。类似于私有方法,用于封装组件内部的 UI 逻辑。
- 用法
- 在组件类内部使用
@LocalBuilder
装饰方法。 - 方法返回 UI 组件,只能在当前组件的
build()
中调用。
- 在组件类内部使用
- 示例
@Component
struct MyComponent {@LocalBuilderprivate buildHeader() {Text('头部标题').fontSize(24).fontWeight(FontWeight.Bold)}build() {Column() {this.buildHeader() // 仅在当前组件内可用Text('主体内容')}}
}
(2)和局部@Builder使用区别
@Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向子组件Child的实例。
@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向父组件Parent的实例。
(3)参数传递规则
- 传入一个对象字面量参数时按引用传递,其他方式按值传递。
- 按引用传递参数
- 按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@LocalBuilder函数内的UI刷新。
当子组件引用父组件的@LocalBuilder函数并传入状态变量时,状态变量的改变不会触发@LocalBuilder函数内的UI刷新。这是因为调用@LocalBuilder装饰的函数创建出来的组件绑定于父组件,而状态变量的刷新机制仅作用于当前组件及其子组件,对父组件无效。而使用@Builder修饰函数可触发UI刷新,原因在于@Builder改变了函数的this指向,使创建出来的组件绑定到子组件上,从而在子组件修改变量能够实现@Builder中的UI刷新。
组件Child将状态变量传递到Parent的@Builder和@LocalBuilder函数内。在@Builder函数内,this指向Child,参数变化能触发UI刷新。在@LocalBuilder函数内,this指向Parent,参数变化不会触发UI刷新。若@LocalBuilder函数内引用Parent的状态变量发生变化,UI能正常刷新。
- 按值传递参数
调用@LocalBuilder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@LocalBuilder函数内的UI刷新。
3、@BuilderParam(引用@Builder)
(1)概念
- 作用
在 @Builder
函数中声明可被外部覆盖的参数,类似于组件的 @Prop
,但仅用于构建器。
-
用法
- 在
@Builder
函数内部使用@BuilderParam
装饰变量。 - 可提供默认值,外部可通过参数覆盖。
- 在
-
示例
@Component
struct son {// 1.定义 BuilderParam 接受外部传入的 UI,并设置默认值// 如果没有尾随闭包,则使用默认@BuilderParam contentBuilder: () => void = this.defaultBuilder// 默认的 Builder@BuilderdefaultBuilder() {Text('awfawsgfwagf').backgroundColor(Color.Pink).width('100%')}build() {Column() {// 2.使用 @BuilderParam 装饰的成员变量this.contentBuilder()}}
}@Entry
@Component
struct Index {build() {Column() {son() {// 直接传递进来给到 BuilderParam(尾随闭包 -》即跟在 son 后面的花括号和里面的内容)Button('sdfvadfvasf')}}}
}
(2)多个 @BuilderParam 参数
子组件拥有多个 BuilderParam,必须通过参数的方式传入
@Component
struct Child {label: string = 'Child';@BuildercustomBuilder() {}@BuildercustomChangeThisBuilder() {}@BuilderParam customBuilderParam: () => void = this.customBuilder;@BuilderParam customChangeThisBuilderParam: () => void = this.customChangeThisBuilder;build() {Column() {this.customBuilderParam()this.customChangeThisBuilderParam()}}
}@Entry
@Component
struct Parent {label: string = 'Parent';@BuildercomponentBuilder() {Text(`${this.label}`)}build() {Column() {// 调用this.componentBuilder()时,this指向当前@Entry所装饰的Parent组件,即label变量的值为"Parent"。this.componentBuilder()Child({// 把this.componentBuilder传给子组件Child的@BuilderParam customBuilderParam,// this指向的是子组件Child,即label变量的值为"Child"。customBuilderParam: this.componentBuilder,// 把():void=>{this.componentBuilder()}传给子组件Child的@BuilderParam customChangeThisBuilderParam,// 因为箭头函数的this指向的是宿主对象,所以label变量的值为"Parent"。customChangeThisBuilderParam: (): void => {this.componentBuilder()}})}}
}
三、结构重用
1、@Extend
装饰器
- 作用
扩展现有组件的样式或事件,创建自定义变体,而无需重写整个组件。
- @Extend支持封装指定组件的私有属性、私有事件和自身定义的全局方法,
- @Extend装饰的方法支持参数。
- @Extend的参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染。
- @Extend仅支持在全局定义,不支持在组件内部定义。
-
用法
-
用
@Extend
装饰类,继承自目标组件类。 -
在类中使用
override
关键字重写样式或事件方法。 -
示例:扩展
Button
组件
@Extend(Button)
class MyButton {// 重写默认样式override width: string = '100%'override backgroundColor: ResourceStr = '#007DFF'override fontColor: ResourceStr = '#FFFFFF'override fontSize: number = 18// 添加自定义事件@Link private count: number = 0constructor(count: Link<number>) {this.count = count}// 扩展点击事件override build() {super.build().onClick(() => {this.count++console.log('按钮被点击,计数:', this.count)})}
}@Entry
@Component
struct MainPage {@State count: number = 0build() {Column() {// 使用扩展后的按钮MyButton(this.$count) {Text(`点击 ${this.count} 次`)}}}
}
- 关键点
- 继承特性:保留原组件的所有属性和方法。
- 样式覆盖:通过
override
直接修改属性默认值。
2、@Styles
装饰器
- 作用
@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。通过@Styles装饰器可以快速定义并复用自定义样式。
- @Styles方法不能有参数,编译期会报错,表明@Styles方法不支持参数。
- 不支持在@Styles方法内使用逻辑组件,逻辑组件内的属性不生效。
-
用法
-
定义在组件内的@Styles可以通过this访问组件的常量和状态变量,并可以在@Styles里通过事件来改变状态变量的值。
-
在组件中通过
@Styles
引用该接口,并应用样式。
-
-
示例
@Entry
@Component
struct FancyUse {@State heightValue: number = 50;@Stylesfancy() {.height(this.heightValue).backgroundColor(Color.Blue).onClick(() => {this.heightValue = 100;})}build() {Column() {Button('change height').fancy()}.height('100%').width('100%')}
}
关键点
- 样式复用:通过
apply()
方法将样式应用到组件。 - 类型安全:样式接口确保属性类型匹配。
- 灵活组合:可组合多个样式(如
.apply(style1).apply(style2)
)。
四、装饰器对比
装饰器 | 核心用途 | 应用对象 | 生命周期 | 复用级别 |
---|---|---|---|---|
@Extend | 扩展现有组件的样式和功能 | 组件类 | 有 | 组件级 |
@Styles | 抽取和复用通用样式和事件配置 | 接口 / 对象 | 无 | 样式级 |
@Builder | 创建可复用的 UI 构建函数 | 函数 | 无 | UI 片段级 |
五、使用场景
@Extend
- 自定义系统组件(如
Button
、Text
)。 - 需要保留原组件特性并添加额外功能。
- 自定义系统组件(如
@Styles
- 统一应用风格(如按钮、卡片的通用样式)。
- 多组件共享相同样式配置。
@Builder
- 封装复杂 UI 结构(如列表项、表单)。
- 实现 UI 组件的参数化配置。
六、注意事项
- 性能考量:
@Builder
函数在每次渲染时执行,避免在其中进行复杂计算。@Extend
适用于少量定制,过度扩展可能导致代码冗余。
- 类型约束:
@Styles
接口需确保属性类型与目标组件匹配。@Builder
参数需明确定义类型,提高代码可读性。
- 组合使用:
- 可组合使用这些装饰器(如在
@Builder
中应用@Styles
)。
- 可组合使用这些装饰器(如在