在开始之前,我们先明确自定义组件和页面的关系:
- 自定义组件:@Component 装饰的 UI 单元,可以组合多个系统组件实现 UI 的复用,可以调用组件的生命周期。
- 页面:即应用的 UI 页面。可以由一个或者多个自定义组件组成,@Entry 装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个 @Entry。只有被 @Entry 装饰的组件才可以调用页面的生命周期。
页面生命周期,即被 @Entry 装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用 @Component 装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其 build () 函数之前执行。
- onDidBuild:组件 build () 函数执行完成之后回调该接口,不建议在 onDidBuild 函数中更改状态变量、使用 animateTo 等功能,这可能会导致不稳定的 UI 表现。
- aboutToDisappear:aboutToDisappear 函数在自定义组件析构销毁之前执行。不允许在 aboutToDisappear 函数中改变状态变量,特别是 @Link 变量的修改可能会导致应用程序行为不稳定。
自定义组件的创建和渲染流程#
- 自定义组件的创建:自定义组件的实例由 ArkUI 框架创建。
- 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
- 如果开发者定义了 aboutToAppear,则执行 aboutToAppear 方法。
- 在首次渲染的时候,执行 build 方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。
- 如果开发者定义了 onDidBuild,则执行 onDidBuild 方法
自定义组件重新渲染#
当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者 LocalStorage / AppStorage 中的属性更改,并导致绑定的状态变量更改其值时:
- 框架观察到了变化,将启动重新渲染。
- 根据框架持有的两个 map(自定义组件的创建和渲染流程中第 4 步),框架可以知道该状态变量管理了哪些 UI 组件,以及这些 UI 组件对应的更新函数。执行这些 UI 组件的更新函数,实现最小化更新。
自定义组件的删除#
如果 if 组件的分支改变,或者 ForEach 循环渲染中数组的个数改变,组件将被删除:
- 在删除组件之前,将调用其 aboutToDisappear 生命周期函数,标记着该节点将要被销毁。ArkUI 的节点删除机制是:后端节点直接从组件树上摘下,后端节点被销毁,对前端节点解引用,前端节点已经没有引用时,将被 JS 虚拟机垃圾回收。
- 自定义组件和它的变量将被删除,如果其有同步的变量,比如 @Link、@Prop、@StorageLink,将从同步源上取消注册。
不建议在生命周期 aboutToDisappear 内使用 async await,如果在生命周期的 aboutToDisappear 使用异步操作(Promise 或者回调方法),自定义组件将被保留在 Promise 的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。
以下示例展示了生命周期的调用时机:
// Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
@State btnColor:string = "#FF007DFF";
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageShow() {
console.info('Index onPageShow');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onPageHide() {
console.info('Index onPageHide');
}
// 只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress() {
console.info('Index onBackPress');
this.btnColor ="#FFEE0606";
return true // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
}
// 组件生命周期
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// 组件生命周期
onDidBuild() {
console.info('MyComponent onDidBuild');
}
// 组件生命周期
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// this.showChild为true,创建Child子组件,执行Child aboutToAppear
if (this.showChild) {
Child()
}
// this.showChild为false,删除Child子组件,执行Child aboutToDisappear
Button('delete Child')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(() => {
this.showChild = false;
})
// push到page页面,执行onPageHide
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/page' });
})
}
}
}
@Component
struct Child {
@State title: string = 'Hello World';
// 组件生命周期
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear')
}
// 组件生命周期
onDidBuild() {
console.info('[lifeCycle] Child onDidBuild');
}
// 组件生命周期
aboutToAppear() {
console.info('[lifeCycle] Child aboutToAppear')
}
build() {
Text(this.title)
.fontSize(50)
.margin(20)
.onClick(() => {
this.title = 'Hello ArkUI';
})
}
}
// page.ets
@Entry
@Component
struct page {
@State textColor: Color = Color.Black;
@State num: number = 0;
onPageShow() {
this.num = 5;
}
onPageHide() {
console.log("page onPageHide");
}
onBackPress() { // 不设置返回值按照false处理
this.textColor = Color.Grey;
this.num = 0;
}
aboutToAppear() {
this.textColor = Color.Blue;
}
build() {
Column() {
Text(`num 的值为:${this.num}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(this.textColor)
.margin(20)
.onClick(() => {
this.num += 5;
})
}
.width('100%')
}
}
以上示例中,Index 页面包含两个自定义组件,一个是被 @Entry 装饰的 MyComponent,也是页面的入口组件,即页面的根节点;一个是 Child,是 MyComponent 的子组件。只有 @Entry 装饰的节点才可以使页面级别的生命周期方法生效,因此在 MyComponent 中声明当前 Index 页面的页面生命周期函数(onPageShow /onPageHide/onBackPress)。MyComponent 和其子组件 Child 分别声明了各自的组件级别生命周期函数(aboutToAppear /onDidBuild/aboutToDisappear)。
-
应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> MyComponent onDidBuild--> Child aboutToAppear --> Child build --> Child onDidBuild --> Index onPageShow。
-
点击 “delete Child”,if 绑定的 this.showChild 变成 false,删除 Child 组件,会执行 Child aboutToDisappear 方法。
-
点击 “push to next page”,调用 router.pushUrl 接口,跳转到另外一个页面,当前 Index 页面隐藏,执行页面生命周期 Index onPageHide。此处调用的是 router.pushUrl 接口,Index 页面被隐藏,并没有销毁,所以只调用 onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。
-
如果调用的是 router.replaceUrl,则当前 Index 页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的 aboutToDisappear,再调用子组件的 aboutToDisappear,然后执行初始化新页面的生命周期流程。
-
点击返回按钮,触发页面生命周期 Index onBackPress,且触发返回一个页面后会导致当前 Index 页面被销毁。
-
最小化应用或者应用进入后台,触发 Index onPageHide。当前 Index 页面没有被销毁,所以并不会执行组件的 aboutToDisappear。应用回到前台,执行 Index onPageShow。
-
退出应用,执行 Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。
自定义组件监听页面生命周期#
使用无感监听页面路由的能力,能够实现在自定义组件中监听页面的生命周期。
// Index.ets
import { uiObserver, router, UIObserver } from '@kit.ArkUI';
@Entry
@Component
struct Index {
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
console.log(`Index onPageShow`);
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
console.log(`Index onPageHide`);
}
}
}
aboutToAppear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate', this.listener);
}
aboutToDisappear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate', this.listener);
}
build() {
Column() {
Text(`this page is ${this.queryRouterPageInfo()?.pageId}`)
.fontSize(25)
Button("push self")
.onClick(() => {
router.pushUrl({
url: 'pages/Index'
})
})
Column() {
SubComponent()
}
}
}
}
@Component
struct SubComponent {
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
console.log(`SubComponent onPageShow`);
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
console.log(`SubComponent onPageHide`);
}
}
}
aboutToAppear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate', this.listener);
}
aboutToDisappear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate', this.listener);
}
build() {
Column() {
Text(`SubComponent`)
}
}
}
此文由 Mix Space 同步更新至 xLog
原始链接为 http://www.sroxck.top/posts/harmony/arkts-life