欢迎光临
我们一直在努力

鸿蒙应用开发UI基础第二十一节:自定义组件与页面的生命周期

【学习目标】

  • 理解生命周期的核心概念,区分自定义组件生命周期和页面生命周期的本质差异;
  • 掌握核心生命周期方法,明确各方法的触发时机及使用规范;
  • 掌握自定义组件/页面的完整生命周期流程,理解嵌套组件的生命周期调用时序;
  • 能在正确的生命周期阶段执行对应操作(初始化、资源清理、数据请求等),规避内存泄漏等问题;
  • 结合实战示例,理解生命周期在实际开发中的应用场景。
  • 一、生命周期核心概念

    1.1 什么是生命周期?

    页面/组件的生命周期,是指用@Component/@ComponentV2装饰的自定义组件从创建、渲染、更新到销毁的整个阶段,每个阶段都有明确的触发时机和核心职责。

    简单来说:

    • 生命周期规定了「组件/页面在什么时间该做什么事」;
    • 遵循生命周期规范,能避免无效操作、内存泄漏、数据不一致等问题。

    1.2 生命周期分类

    • 自定义组件通用生命周期:包含aboutToAppear、onDidBuild、aboutToDisappear,仅需@Component/@ComponentV2装饰器即可生效,所有自定义组件通用;其中onDidBuild仅在首次渲染时触发,后续组件重新渲染不再执行。
    • 页面专属生命周期:在自定义组件生命周期基础上,新增onPageShow、onPageHide,需@Entry + @Component装饰器配合使用,仅在根页面生效,赋予页面前后台切换能力。

    二、工程目录结构

    本节示例基于鸿蒙 API12+ Stage 模型工程,核心目录结构如下(仅展示关键文件):

    LifeCycleDemo/
    ├── AppScope/
    │ └── app.json5 // 应用全局配置
    ├── entry/
    │ ├── src/
    │ │ ├── main/
    │ │ │ ├── ets/
    │ │ │ │ ├── entryability/
    │ │ │ │ │ └── EntryAbility.ets // 应用入口能力
    │ │ │ │ ├── components/ // 自定义组件目录
    │ │ │ │ │ └── LifeCycleDemoComponent.ets // 自定义组件示例
    │ │ │ │ ├── pages/ // 页面目录
    │ │ │ │ │ ├── Index.ets // 首页(跳转NextPage)
    │ │ │ │ │ ├── NextPage.ets // 第二层页面
    │ │ │ │ │ └── OtherPage.ets // 第三层页面
    │ │ │ ├── resources/ // 资源目录(图片、字符串等)
    │ │ │ └── module.json5 // 模块配置
    │ │ └── oh-package.json5 // 依赖配置
    │ └── build-profile.json5 // 构建配置
    └── hvigorfile.ts // 构建脚本

    三、核心生命周期方法详解

    3.1 自定义组件核心生命周期

    方法名触发时机核心作用禁止操作
    aboutToAppear 组件实例创建完成后、build()执行前 初始化组件数据(修改@State、绑定变量)、发起首屏页面数据异步请求 执行同步耗时操作(阻塞渲染)
    onDidBuild 组件首次渲染build()完成后 埋点上报、获取组件尺寸等轻量无UI影响操作 修改响应式状态变量(触发二次渲染)、耗时操作
    aboutToDisappear 组件销毁前 清理资源(清除定时器、取消网络请求、解绑监听) 修改状态变量、使用async/await(阻碍垃圾回收)

    3.2 页面专属生命周期(仅 @Entry + @Component 生效)

    方法名触发时机核心作用注意事项
    onPageShow 页面显示时(首次进入/切回前台/路由返回) 恢复数据、刷新非关键请求、启动动画 需做防抖处理,避免频繁触发
    onPageHide 页面隐藏时(跳转页面/切后台) 暂停操作(定时器/媒体)、保存临时状态 避免不必要耗时操作,优先轻量处理

    3.3 自定义组件生命周期流程

    #mermaid-svg-isaYMrbcmguI5yWA{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-isaYMrbcmguI5yWA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-isaYMrbcmguI5yWA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-isaYMrbcmguI5yWA .error-icon{fill:#552222;}#mermaid-svg-isaYMrbcmguI5yWA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-isaYMrbcmguI5yWA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-isaYMrbcmguI5yWA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-isaYMrbcmguI5yWA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-isaYMrbcmguI5yWA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-isaYMrbcmguI5yWA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-isaYMrbcmguI5yWA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-isaYMrbcmguI5yWA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-isaYMrbcmguI5yWA .marker.cross{stroke:#333333;}#mermaid-svg-isaYMrbcmguI5yWA svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-isaYMrbcmguI5yWA p{margin:0;}#mermaid-svg-isaYMrbcmguI5yWA .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-isaYMrbcmguI5yWA .cluster-label text{fill:#333;}#mermaid-svg-isaYMrbcmguI5yWA .cluster-label span{color:#333;}#mermaid-svg-isaYMrbcmguI5yWA .cluster-label span p{background-color:transparent;}#mermaid-svg-isaYMrbcmguI5yWA .label text,#mermaid-svg-isaYMrbcmguI5yWA span{fill:#333;color:#333;}#mermaid-svg-isaYMrbcmguI5yWA .node rect,#mermaid-svg-isaYMrbcmguI5yWA .node circle,#mermaid-svg-isaYMrbcmguI5yWA .node ellipse,#mermaid-svg-isaYMrbcmguI5yWA .node polygon,#mermaid-svg-isaYMrbcmguI5yWA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-isaYMrbcmguI5yWA .rough-node .label text,#mermaid-svg-isaYMrbcmguI5yWA .node .label text,#mermaid-svg-isaYMrbcmguI5yWA .image-shape .label,#mermaid-svg-isaYMrbcmguI5yWA .icon-shape .label{text-anchor:middle;}#mermaid-svg-isaYMrbcmguI5yWA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-isaYMrbcmguI5yWA .rough-node .label,#mermaid-svg-isaYMrbcmguI5yWA .node .label,#mermaid-svg-isaYMrbcmguI5yWA .image-shape .label,#mermaid-svg-isaYMrbcmguI5yWA .icon-shape .label{text-align:center;}#mermaid-svg-isaYMrbcmguI5yWA .node.clickable{cursor:pointer;}#mermaid-svg-isaYMrbcmguI5yWA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-isaYMrbcmguI5yWA .arrowheadPath{fill:#333333;}#mermaid-svg-isaYMrbcmguI5yWA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-isaYMrbcmguI5yWA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-isaYMrbcmguI5yWA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-isaYMrbcmguI5yWA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-isaYMrbcmguI5yWA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-isaYMrbcmguI5yWA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-isaYMrbcmguI5yWA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-isaYMrbcmguI5yWA .cluster text{fill:#333;}#mermaid-svg-isaYMrbcmguI5yWA .cluster span{color:#333;}#mermaid-svg-isaYMrbcmguI5yWA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-isaYMrbcmguI5yWA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-isaYMrbcmguI5yWA rect.text{fill:none;stroke-width:0;}#mermaid-svg-isaYMrbcmguI5yWA .icon-shape,#mermaid-svg-isaYMrbcmguI5yWA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-isaYMrbcmguI5yWA .icon-shape p,#mermaid-svg-isaYMrbcmguI5yWA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-isaYMrbcmguI5yWA .icon-shape rect,#mermaid-svg-isaYMrbcmguI5yWA .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-isaYMrbcmguI5yWA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-isaYMrbcmguI5yWA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-isaYMrbcmguI5yWA :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    框架创建组件实例

    初始化成员变量(定义顺序)

    执行aboutToAppear

    执行build(仅解析结构,不渲染子组件)

    执行onDidBuild(仅首次渲染)

    组件销毁前执行aboutToDisappear

    四、自定义组件生命周期示例

    // entry/src/main/ets/components/LifeCycleDemoComponent.ets
    import { hilog } from '@kit.PerformanceAnalysisKit';

    @Component
    export struct LifeCycleDemoComponent {
    // 响应式状态变量
    @State count: number = 0;
    private timerId: number | null = null;

    // 1. 组件即将渲染
    aboutToAppear() {
    hilog.info(0x0000, 'testTag', '[子组件] LifeCycleDemoComponent: aboutToAppear');
    // 允许修改状态变量
    this.count = 10;
    }

    // 2. 组件首次渲染build完成后触发(仅执行一次)
    onDidBuild() {
    hilog.info(0x0000, 'testTag', '[子组件] LifeCycleDemoComponent: onDidBuild');
    // 轻量 UI 操作(如获取组件尺寸,禁止修改状态,很少用到)
    }

    // 3. 组件即将销毁
    aboutToDisappear() {
    hilog.info(0x0000, 'testTag', '[子组件] LifeCycleDemoComponent: aboutToDisappear');
    // 彻底清理资源(防止内存泄漏)
    if (this.timerId) {
    clearInterval(this.timerId);
    this.timerId = null;
    }
    hilog.info(0x0000, 'testTag', '[子组件] 销毁前count值:%{public}d', this.count);
    // 禁止:修改状态变量、使用async/await、新增异步操作
    }

    // 页面特有生命周期在子组件中实现不会执行
    onPageShow(): void {
    hilog.info(0x0000, 'testTag', '[子组件] 组件已经显示:不会执行');
    }

    onPageHide(): void {
    hilog.info(0x0000, 'testTag', '[子组件] 组件已经隐藏:不会执行');
    }

    build() {
    Column({ space: 10 }) {
    Text(`自定义组件计数:${this.count}`)
    .fontSize(20)
    .fontColor('#333');
    Button("启动定时器(子组件)")
    .onClick(() => {
    // 事件回调中允许启动异步资源
    this.timerId = setInterval(() => {
    this.count++;
    hilog.info(0x0000, 'testTag', '[子组件] 定时器更新count:%{public}d', this.count);
    }, 1000);
    })
    .margin({ top: 10 });
    Button("重置计数")
    .onClick(() => {
    this.count = 0;
    })
    .margin({ top: 10 });
    }
    .padding(20)
    .backgroundColor('#f5f5f5')
    .borderRadius(12);
    }
    }

    五、页面生命周期示例

    5.1 首页(Index.ets,跳转NextPage)

    // entry/src/main/ets/pages/Index.ets
    import { router } from '@kit.ArkUI';
    import { hilog } from '@kit.PerformanceAnalysisKit';

    @Entry
    @Component
    struct Index {
    build() {
    Column({ space: 20 }) {
    Text("生命周期演示首页")
    .fontSize(24)
    .fontWeight(FontWeight.Bold);
    Text("点击按钮跳转到 NextPage,查看控制台日志")
    .fontSize(16)
    .fontColor('#666');

    Button("跳转到 NextPage")
    .onClick(() => {
    router.pushUrl({
    url: "pages/NextPage"
    }).catch((err: BusinessError) => {
    hilog.error(0x0000, 'testTag', '[首页] 跳转失败:%{public}s', err.message);
    });
    })
    .backgroundColor('#007dff')
    .fontColor(Color.White)
    .margin({ top: 10 });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
    }
    }

    5.2 第二层页面(NextPage.ets)

    // entry/src/main/ets/pages/NextPage.ets
    import { router } from '@kit.ArkUI';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { LifeCycleDemoComponent } from '../components/LifeCycleDemoComponent';

    @Entry
    @Component
    struct NextPage {
    @State showChild: boolean = true; // 控制子组件显示/隐藏
    private pageTimerId: number | null = null;

    // ========== 组件通用生命周期 ==========
    aboutToAppear() {
    hilog.info(0x0000, 'testTag', '[页面] NextPage: aboutToAppear');
    }

    onDidBuild() {
    hilog.info(0x0000, 'testTag', '[页面] NextPage: onDidBuild');
    // 页面首次渲染build完成后执行(仅一次)
    //【关键】父组件onDidBuild执行完成后,子组件才会触发aboutToAppear(懒展开)
    }

    aboutToDisappear() {
    hilog.info(0x0000, 'testTag', '[页面] NextPage: aboutToDisappear');
    // 最终清理页面级资源
    if (this.pageTimerId) {
    clearInterval(this.pageTimerId);
    this.pageTimerId = null;
    }
    }

    // ========== 页面特有生命周期(核心演示onPageHide/onPageShow) ==========
    onPageShow() {
    hilog.info(0x0000, 'testTag', '[页面] NextPage: onPageShow');
    // 启动页面级定时器(演示用)
    this.pageTimerId = setInterval(() => {
    hilog.info(0x0000, 'testTag', '[页面] NextPage 定时器运行中…');
    }, 2000);
    }

    onPageHide() {
    hilog.info(0x0000, 'testTag', '[页面] NextPage: onPageHide');
    // 暂停页面级定时器(核心:跳转OtherPage时触发)
    if (this.pageTimerId) {
    clearInterval(this.pageTimerId);
    this.pageTimerId = null;
    }
    }

    build() {
    Column({ space: 20 }) {
    Text("NextPage(第二层页面)")
    .fontSize(24)
    .fontWeight(FontWeight.Bold);
    Text("跳转OtherPage会触发onPageHide,返回会触发onPageShow")
    .fontSize(16)
    .fontColor('#666');

    // 控制子组件显示/隐藏
    Button(this.showChild ? '隐藏子组件' : '显示子组件')
    .margin(10)
    .backgroundColor('#FF007DFF')
    .onClick(() => {
    this.showChild = !this.showChild;
    });

    if (this.showChild) {
    LifeCycleDemoComponent();
    }

    // 跳转第三层页面OtherPage
    Button("跳转到 OtherPage(第三层)")
    .onClick(() => {
    router.pushUrl({
    url: "pages/OtherPage"
    }).catch((err: BusinessError) => {
    hilog.error(0x0000, 'testTag', '[NextPage] 跳转失败:%{public}s', err.message);
    });
    })
    .backgroundColor('#FF9900')
    .fontColor(Color.White)
    .margin({ top: 10 });

    // 返回首页
    Button("返回首页")
    .onClick(() => {
    router.back()
    })
    .backgroundColor('#666')
    .fontColor(Color.White)
    .margin({ top: 10 });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20);
    }
    }

    5.3 第三层页面(OtherPage.ets)

    // entry/src/main/ets/pages/OtherPage.ets
    import { router } from '@kit.ArkUI';
    import { hilog } from '@kit.PerformanceAnalysisKit';

    @Entry
    @Component
    struct OtherPage {
    // ========== 组件通用生命周期 ==========
    aboutToAppear() {
    hilog.info(0x0000, 'testTag', '[页面] OtherPage: aboutToAppear');
    }

    onDidBuild() {
    hilog.info(0x0000, 'testTag', '[页面] OtherPage: onDidBuild');
    }

    aboutToDisappear() {
    hilog.info(0x0000, 'testTag', '[页面] OtherPage: aboutToDisappear');
    }

    // ========== 页面特有生命周期 ==========
    onPageShow() {
    hilog.info(0x0000, 'testTag', '[页面] OtherPage: onPageShow');
    }

    onPageHide() {
    hilog.info(0x0000, 'testTag', '[页面] OtherPage: onPageHide');
    }

    build() {
    Column({ space: 20 }) {
    Text("OtherPage(第三层页面)")
    .fontSize(24)
    .fontWeight(FontWeight.Bold);

    Button("返回 NextPage(第二层)")
    .onClick(() => {
    router.back()
    })
    .backgroundColor('#007dff')
    .fontColor(Color.White)
    .margin({ top: 10 });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .padding(20);
    }
    }

    六、生命周期日志与核心时序验证

    Index页面未添加生命周期回调,仅打印其他页面的生命周期日志(过滤无关内容)。

    6.1 首页点击「跳转到 NextPage」

    [页面] NextPage: aboutToAppear // 父组件aboutToAppear执行
    [页面] NextPage: onDidBuild // 父组件onDidBuild执行(懒展开:执行完后子组件才启动)
    [子组件] LifeCycleDemoComponent: aboutToAppear // 子组件aboutToAppear执行
    [子组件] LifeCycleDemoComponent: onDidBuild // 子组件onDidBuild执行
    [页面] NextPage: onPageShow // 父页面onPageShow(所有子组件渲染完成后触发)

    核心结论(懒展开特性):
    应用冷启动下嵌套组件初始化流程:
    父组件 aboutToAppear → 父组件 build → 父组件 onDidBuild → 子组件 aboutToAppear → 子组件 build → 子组件 onDidBuild → 父组件 onPageShow

    • 父组件执行onDidBuild后,子组件才触发aboutToAppear,是鸿蒙自定义组件的懒展开/懒渲染特性;
    • 懒展开核心价值:提升首屏性能,父组件优先完成基础渲染,子组件延迟初始化,避免首屏阻塞。

    6.2 NextPage点击「隐藏子组件」

    [子组件] LifeCycleDemoComponent: aboutToDisappear

    6.3 NextPage点击「显示子组件」

    [子组件] LifeCycleDemoComponent: aboutToAppear
    [子组件] LifeCycleDemoComponent: onDidBuild

    说明:if/else 控制组件显示/隐藏并非“视觉隐藏”,而是从组件树中新增/删除组件,因此会触发完整的生命周期。

    6.4 NextPage点击「跳转到 OtherPage(第三层)」

    [页面] OtherPage: aboutToAppear
    [页面] OtherPage: onDidBuild
    [页面] NextPage: onPageHide
    [页面] OtherPage: onPageShow

    6.5 点击「返回 NextPage(第二层)」

    [页面] OtherPage: onPageHide
    [页面] NextPage: onPageShow
    [页面] OtherPage: aboutToDisappear

    6.6 NextPage点击「返回首页」

    [页面] NextPage: onPageHide
    [页面] NextPage: aboutToDisappear
    [子组件] LifeCycleDemoComponent: aboutToDisappear

    6.7 内存资源占用演示(真机设备)

    操作步骤:
  • 注释NextPage的aboutToDisappear和 onPageHide中定时器清理代码:
  • // if (this.pageTimerId) {
    // clearInterval(this.pageTimerId);
    // this.pageTimerId = null;
    // }

  • 重复操作:首页→NextPage之间多次切换,观察内存:通过Profiler工具查看,内存持续上涨无释放;
  • 恢复清理代码后重复操作:内存占用稳定,定时器被正常释放。
  • 未清理定时器

    生命周期中内存资源占用

    清理定时器

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    生命周期中内存资源及时释放

    七、常见错误与解决方案

    错误1:子组件实现页面专属生命周期方法

    • 问题表现:子组件中定义onPageShow/onPageHide,运行后无日志输出,方法完全不生效;
    • 解决方案:仅在@Entry + @Component装饰的根页面实现onPageShow/onPageHide,子组件仅保留通用生命周期(aboutToAppear/onDidBuild/aboutToDisappear)。

    错误2:aboutToAppear 执行同步耗时操作

    • 问题表现:同步阻塞操作(如大数据解析、复杂计算)导致首屏渲染卡顿、页面启动超时;
    • 解决方案:
    • 将耗时操作拆分为异步任务(setTimeout/Promise),避免阻塞渲染;
    • 非首屏关键操作延后至onPageShow或用户交互事件中执行;

    错误3:aboutToDisappear中执行异步操作或修改状态

    • 问题表现:组件销毁流程阻塞、垃圾回收失败(内存泄漏),或触发二次渲染导致页面闪屏;
    • 解决方案:
    • aboutToDisappear仅保留同步清理逻辑(如clearInterval/clearTimeout、取消网络请求);
    • 需异步清理的操作提前在onPageHide中执行;
    • 禁止修改任何响应式状态变量如@Link会导致程序不稳定。

    错误4:资源未清理

    • 问题表现:页面销毁后定时器仍运行、网络请求仍发送,日志持续输出,内存占用持续上涨;
    • 解决方案:
    • 将定时器ID、请求控制器等存入组件私有变量;
    • 在aboutToDisappear中强制清理资源(如clearInterval/取消请求),确保100%释放。

    八、仓库代码

    • 工程名称:LifeCycleDemo
    • 仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git

    九、下节预告

    下一节我们将学习 ArkTS 全局状态存储(LocalStorage/AppStorage/PersistentStorage)

  • 掌握 LocalStorage(页面级)和 AppStorage(应用级)的核心用法;
  • 结合生命周期实现全局状态的初始化、更新与清理;
  • 掌握 PersistentStorage 基础持久化,解决应用重启数据丢失问题;
  • 实战:实现跨页面数据共享与持久化,规避数据不一致问题。
  • 赞(0)
    未经允许不得转载:171主机测评 » 鸿蒙应用开发UI基础第二十一节:自定义组件与页面的生命周期
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址