harmonyOs ArkTS零基础 - 页面和自定义组件生命周期(三)
页面和自定义组件生命周期
在开始之前,我们先明确自定义组件和页面的关系:
- 自定义组件:@Component 装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
- 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry 装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个 @Entry。只有被 @Entry 装饰的组件才可以调用页面的生命周期。
页面生命周期,即被 @Entry 装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用 @Component 装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其 build() 函数之前执行。
- aboutToDisappear:在自定义组件析构销毁之前执行。不允许在 aboutToDisappear 函数中改变状态变量,特别是 @Link 变量的修改可能会导致应用程序行为不稳定。
onPageShow
onPageShow?(): void
页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效。
onPageHide
onPageHide?(): void
页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效。
onBackPress
onBackPress?(): void
当用户点击返回按钮时触发,仅@Entry装饰的自定义组件生效。
// xxx.ets @Entry @Component struct IndexComponent { @State textColor: Color = Color.Black; onPageShow() { this.textColor = Color.Blue; console.info('IndexComponent onPageShow'); } onPageHide() { this.textColor = Color.Transparent; console.info('IndexComponent onPageHide'); } onBackPress() { this.textColor = Color.Red; console.info('IndexComponent onBackPress'); } build() { Column() { Text('Hello World') .fontColor(this.textColor) .fontSize(30) .margin(30) }.width('100%') } }
如下图:
aboutToAppear
aboutToAppear?(): void
aboutToAppear 函数在创建自定义组件的新实例后,在执行其build()函数之前执行。允许在 aboutToAppear 函数中改变状态变量,更改将在后续执行build()函数中生效。
aboutToDisappear
aboutToDisappear?(): void
aboutToDisappear 函数在自定义组件析构销毁之前执行。不允许在 aboutToDisappear 函数中改变状态变量,特别是 @Link 变量的修改可能会导致应用程序行为不稳定。
生命周期流程如下图所示,下图展示的是被 @Entry 装饰的组件(页面)生命周期。
根据上面的流程图,我们从自定义组件的初始创建、重新渲染和删除来详细解释。
自定义组件的创建和渲染流程
- 自定义组件的创建:自定义组件的实例由ArkUI框架创建。
- 初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。
- 如果开发者定义了 aboutToAppear ,则执行 aboutToAppear 方法。
- 在首次渲染的时候,执行 build 方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。
当应用在后台启动时,此时应用进程并没有销毁,所以仅需要执行 onPageShow。
自定义组件重新渲染
当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者 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 '@ohos.router'; @Entry @Component struct MyComponent { @State showChild: boolean = true; // 只有被@Entry装饰的组件才可以调用页面的生命周期 onPageShow() { console.info('Index onPageShow'); } // 只有被@Entry装饰的组件才可以调用页面的生命周期 onPageHide() { console.info('Index onPageHide'); } // 只有被@Entry装饰的组件才可以调用页面的生命周期 onBackPress() { console.info('Index onBackPress'); } // 组件生命周期 aboutToAppear() { console.info('MyComponent aboutToAppear'); } // 组件生命周期 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').onClick(() => { this.showChild = false; }) // push到Page2页面,执行onPageHide Button('push to next page') .onClick(() => { router.pushUrl({ url: 'pages/Page2' }); }) } } } @Component struct Child { @State title: string = 'Hello World'; // 组件生命周期 aboutToDisappear() { console.info('[lifeCycle] Child aboutToDisappear') } // 组件生命周期 aboutToAppear() { console.info('[lifeCycle] Child aboutToAppear') } build() { Text(this.title).fontSize(50).onClick(() => { this.title = 'Hello ArkUI'; }) } }
以上示例中,Index 页面包含两个自定义组件,一个是被 @Entry 装饰的 MyComponent ,也是页面的入口组件,即页面的根节点;一个是 Child ,是 MyComponent 的子组件。只有 @Entry 装饰的节点才可以使页面级别的生命周期方法生效,所以 MyComponent 中声明了当前 Index 页面的页面生命周期函数。MyComponent 和其子组件 Child 也同时也声明了组件的生命周期函数。
- 应用冷启动的初始化流程为:MyComponent aboutToAppear –> MyComponent build –> Child aboutToAppear –> Child build –> Child build 执行完毕 –> MyComponent build 执行完毕 –> 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。