目录
自定义组件的基本用法
自定义组件的基本结构
struct
@Component
freezeWhenInactive
build()函数
@Entry
EntryOptions
@Reusable
成员函数/变量
自定义组件的参数规定
build()函数
自定义组件生命周期
自定义组件的创建和渲染流程
自定义组件重新渲染
自定义组件的删除
自定义组件嵌套使用与示例
在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。进行UI界面开发时,不仅要组合使用系统组件,还需考虑代码的可复用性、业务逻辑与UI的分离,以及后续版本的演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。
自定义组件的特点:
- 可组合:允许开发者组合使用系统组件、及其属性和方法。
- 可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。
- 数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。
自定义组件的基本用法
@Component
struct HelloComponent {@State message: string = 'Hello, World!';build() {// HelloComponent自定义组件组合系统组件Row和TextRow() {Text(this.message).onClick(() => {// 状态变量message的改变驱动UI刷新,UI从'Hello, World!'刷新为'Hello, ArkUI!'this.message = 'Hello, ArkUI!';})}}
}
注意
如果在其他文件中引用自定义组件,需要使用export关键字导出组件,并在使用的页面import该自定义组件。
可以在其他自定义组件的build()函数中多次创建HelloComponent,以实现自定义组件的重用。
@Entry
@Component
struct ParentComponent {build() {Column() {Text('ArkUI message')HelloComponent({ message: 'Hello World!' });Divider()HelloComponent({ message: '你好,世界!' });}}
}
自定义组件的基本结构
struct
自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
说明
自定义组件名、类名、函数名不得与系统组件名重复。
@Component
@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的boolean类型参数。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
从API version 11开始,@Component可以接受一个可选的boolean类型参数。
@Component
struct MyComponent {
}
freezeWhenInactive
组件冻结选项。
自定义组件冻结功能专为优化复杂UI页面的性能而设计,尤其适用于包含多个页面栈、长列表或宫格布局的场景。当状态变量绑定多个UI组件时,其变化易触发大量组件刷新,导致界面卡顿与响应延迟。为提升这类高负载UI界面的刷新性能,建议开发者使用自定义组件冻结功能。
组件冻结功能是一种性能优化机制,它会冻结非激活状态下的组件的刷新能力。当组件处于非激活状态时,即使其绑定的状态变量发生变化,也不会触发该组件的UI重新渲染,从而降低复杂UI场景下的刷新负载。
组件冻结的工作原理是:
- 开发者通过设置freezeWhenInactive属性,即可激活组件冻结机制。
- 启用后,系统将仅对处于激活状态的自定义组件进行更新,这使得UI框架可以尽量缩小更新范围,仅限于用户可见范围内(激活状态)的自定义组件,从而提高复杂UI场景下的刷新效率。
- 当之前处于inactive状态的自定义组件重新变为active状态时,状态管理框架会对其执行必要的刷新操作,确保UI的正确展示。
需要注意,组件active/inactive并不等同于其可见性。组件冻结目前仅适用于以下场景:
- 页面路由:当前栈顶页面为active状态,非栈顶不可见页面为inactive状态。
- TabContent:只有当前显示的TabContent中的自定义组件处于active状态,其余则为inactive。
- LazyForEach:仅当前显示的LazyForEach中的自定义组件为active状态,而缓存节点的组件则为inactive状态。
- Navigation:当前显示的NavDestination中的自定义组件为active状态,而其他未显示的NavDestination组件则为inactive状态。
- 组件复用:进入复用池的组件为inactive状态,从复用池上树的节点为active状态。
- 混用场景:对于以上场景的组合使用,例如TabContent下面使用LazyForEach,切换Tab时,API version 17及以下,LazyForEach中的所有节点都会被设置为active状态,而从API version 18开始,只有LazyForEach的屏上节点会被设置为active状态,其余则为inactive状态。
名称 | 类型 | 必填 | 说明 |
---|---|---|---|
freezeWhenInactive | boolean | 否 | 是否开启组件冻结。默认值false。true:开启组件冻结,false:不开启组件冻结。 |
@Component({ freezeWhenInactive: true })
struct MyComponent {
}
build()函数
build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
@Component
struct MyComponent {build() {}
}
@Entry
@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,仅允许存在一个由@Entry装饰的自定义组件作为页面的入口。@Entry可以接受一个可选的LocalStorage的参数。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。
从API version 11开始,该装饰器支持在元服务中使用。
@Entry
@Component
struct MyComponent {
}
EntryOptions
命名路由跳转选项。
名称 | 类型 | 必填 | 说明 |
---|---|---|---|
routeName | string | 否 | 表示作为命名路由页面的名字。 |
storage | LocalStorage | 否 | 页面级的UI状态存储。 |
useSharedStorage | boolean | 否 | 是否使用LocalStorage.getShared()接口返回的共享的LocalStorage实例对象。默认值false。true:使用共享的LocalStorage实例对象。false:不使用共享的LocalStorage实例对象。 |
说明
当useSharedStorage设置为true,并且storage也被赋值时,useSharedStorage的值优先级更高。
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
@Reusable
@Reusable装饰的自定义组件具备可复用能力。详细请参考:@Reusable装饰器:组件复用。
说明
从API version 10开始,该装饰器支持在ArkTS卡片中使用。
@Reusable
@Component
struct MyComponent {
}
成员函数/变量
自定义组件除了必须要实现build()函数外,还可以实现其他成员函数,成员函数具有以下约束:
- 自定义组件的成员函数为私有的,且不建议声明为静态函数。
自定义组件可以包含成员变量,成员变量具有以下约束:
- 自定义组件的成员变量为私有的,且不建议声明成静态变量。
- 自定义组件的成员变量本地初始化有些是可选的,有些是必选的。
自定义组件的参数规定
以上示例中,可以在build方法里创建自定义组件,同时在创建自定义组件的过程中,根据装饰器的规则来初始化自定义组件的参数。
@Component
struct MyComponent {private countDownFrom: number = 0;private color: Color = Color.Blue;build() {}
}@Entry
@Component
struct ParentComponent {private someColor: Color = Color.Pink;build() {Column() {// 创建MyComponent实例,并将创建MyComponent成员变量countDownFrom初始化为10,将成员变量color初始化为this.someColorMyComponent({ countDownFrom: 10, color: this.someColor })}}
}
以下示例代码将父组件中的函数传递给子组件,并在子组件中调用。
@Entry
@Component
struct Parent {@State cnt: number = 0submit: () => void = () => {this.cnt++;}build() {Column() {Text(`${this.cnt}`)Son({ submitArrow: this.submit })}}
}@Component
struct Son {submitArrow?: () => voidbuild() {Row() {Button('add').width(80).onClick(() => {if (this.submitArrow) {this.submitArrow()}})}.height(56)}
}
build()函数
所有在build()函数中声明的语句统称为UI描述,UI描述需要遵循以下规则:
- @Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点
- @Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。
@Entry @Component struct MyComponent {build() {// 根节点唯一且必要,必须为容器组件Row() {ChildComponent() }} }@Component struct ChildComponent {build() {// 根节点唯一且必要,可为非容器组件Image('test.jpg')} }
- 不允许声明本地变量,反例如下。
build() {// 反例:不允许声明本地变量let num: number = 1; }
- 不允许在UI描述里直接使用console.info,但允许在方法或者函数里使用,反例如下。
build() {// 反例:不允许console.infoconsole.info('print debug log'); }
- 不允许创建本地的作用域,反例如下。
build() {// 反例:不允许本地作用域{// ...} }
- 不允许调用没有用@Builder装饰的方法,允许系统组件的参数是TS方法的返回值。
@Component struct ParentComponent {doSomeCalculations() {}calcTextValue(): string {return 'Hello World';}@Builder doSomeRender() {Text(`Hello World`)}build() {Column() {// 反例:不能调用没有用@Builder装饰的方法this.doSomeCalculations();// 正例:可以调用this.doSomeRender();// 正例:参数可以为调用TS方法的返回值Text(this.calcTextValue())}} }
- 不允许使用switch语法,当需要使用条件判断时,请使用if。示例如下。
build() {Column() {// 反例:不允许使用switch语法switch (expression) {case 1:Text('...')break;case 2:Image('...')break;default:Text('...')break;}// 正例:使用ifif(expression == 1) {Text('...')} else if(expression == 2) {Image('...')} else {Text('...')}} }
- 不允许使用表达式,请使用if组件,示例如下。
build() {Column() {// 反例:不允许使用表达式(this.aVar > 10) ? Text('...') : Image('...')// 正例:使用if判断if(this.aVar > 10) {Text('...')} else {Image('...')}} }
- 不允许直接改变状态变量,反例如下。
@Component struct MyComponent {@State textColor: Color = Color.Yellow;@State columnColor: Color = Color.Green;@State count: number = 1;build() {Column() {// 应避免直接在Text组件内改变count的值Text(`${this.count++}`).width(50).height(50).fontColor(this.textColor).onClick(() => {this.columnColor = Color.Red;})Button("change textColor").onClick(() =>{this.textColor = Color.Pink;})}.backgroundColor(this.columnColor)} }
在ArkUI状态管理中,状态驱动UI更新。
自定义组件生命周期
自定义组件生命周期,即用@Component或@ComponentV2装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build函数之前执行。
- onDidBuild:在组件首次渲染触发的build函数执行完成之后回调该接口,后续组件重新渲染将不回调该接口。开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
- aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
自定义组件生命周期流程如下图所示。
根据上面的流程图,接下来从自定义组件的初始创建、重新渲染和删除来详细说明。
自定义组件的创建和渲染流程
- 自定义组件的创建:自定义组件的实例由ArkUI框架创建。
- 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
- 如果开发者定义了aboutToAppear,则执行该方法。
- 在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。
- 如果开发者定义了onDidBuild,则执行该方法。
自定义组件重新渲染
当触发事件(比如点击)改变状态变量时,或者LocalStorage / AppStorage中的属性更改,并导致绑定的状态变量更改其值时:
- 框架观察到变化,启动重新渲染。
- 根据框架记录的状态变量和组件的映射关系,仅刷新发生变化的状态变量所关联的组件,实现最小化更新。
自定义组件的删除
例如if组件的分支改变或ForEach循环渲染中数组的个数改变,组件将被移除:
- 在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。ArkUI的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被Ark虚拟机垃圾回收。
- 自定义组件和它的变量将被删除,如果组件有同步的变量(如@Link、@Prop、@StorageLink),将从同步源上取消注册。
不建议在生命周期aboutToDisappear中使用async await。如果在此生命周期中使用异步操作(如 Promise 或回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法执行完毕,这会阻止自定义组件的垃圾回收。
自定义组件嵌套使用与示例
通过以下示例,来详细说明自定义组件在嵌套使用时,自定义组件生命周期的调用时序:
// Index.ets
@Entry
@Component
struct Parent {@State showChild: boolean = true;@State btnColor: string = "#FF007DFF";// 组件生命周期aboutToAppear() {console.info('Parent aboutToAppear');}// 组件生命周期onDidBuild() {console.info('Parent onDidBuild');}// 组件生命周期aboutToDisappear() {console.info('Parent aboutToDisappear');}build() {Column() {// this.showChild为true,创建Child子组件,执行Child aboutToAppearif (this.showChild) {Child()}Button('delete Child').margin(20).backgroundColor(this.btnColor).onClick(() => {// 更改this.showChild为false,删除Child子组件,执行Child aboutToDisappear// 更改this.showChild为true,添加Child子组件,执行Child aboutToAppearthis.showChild = !this.showChild;})}}
}@Component
struct Child {@State title: string = 'Hello World';// 组件生命周期aboutToDisappear() {console.info('Child aboutToDisappear');}// 组件生命周期onDidBuild() {console.info('Child onDidBuild');}// 组件生命周期aboutToAppear() {console.info('Child aboutToAppear');}build() {Text(this.title).fontSize(50).margin(20).onClick(() => {this.title = 'Hello ArkUI';})}
}
以上示例中,Index页面包含两个自定义组件,一个是Parent,一个是Child,Parent及其子组件Child分别声明了各自的自定义组件生命周期函数(aboutToAppear / onDidBuild / aboutToDisappear)。
- 应用冷启动的初始化流程为:Parent aboutToAppear --> Parent build --> Parent onDidBuild --> Child aboutToAppear --> Child build --> Child onDidBuild。此处体现了自定义组件懒展开特性,即Parent执行完onDidBuild之后才会执行Child组件的aboutToAppear。日志输出信息如下:
Parent aboutToAppear
Parent onDidBuild
Child aboutToAppear
Child onDidBuild
- 点击Button按钮,更改showChild为false,删除Child组件,执行Child aboutToDisappear方法。
- 如果直接退出应用,则会触发以下生命周期:Parent aboutToDisappear --> Child aboutToDisappear,此处体现了自定义组件删除顺序也是从父到子。日志输出信息如下:
Parent aboutToDisappear
Child aboutToDisappear
- 最小化应用或者应用进入后台,当前Index页面未被销毁,所以并不会执行组件的aboutToDisappear。
- 如果showChild的默认值为false,则应用冷启动的初始化流程为:Parent aboutToAppear --> Parent build --> Parent onDidBuild。日志输出信息如下:
Parent aboutToAppear
Parent onDidBuild
- 如果showChild的默认值为false,直接退出应用,则只执行Parent aboutToDisappear方法。
- 如果showChild的默认值为false,此时点击Button按钮,更改showChild为true,添加Child组件,添加流程为:Child aboutToAppear --> Child build --> Child onDidBuild。日志输出信息如下:
Child aboutToAppear
Child onDidBuild
当showchild为默认值true时,该示例的生命周期流程图如下所示: