HarmonyOS 6.1 组件生命周期全解剖 — aboutToAppear到aboutToRecycle的执行时序

HarmonyOS 6.1 组件生命周期全解剖 — aboutToAppear到aboutToRecycle的执行时序

对应Demo: LifecycleDemo | 难度: 中级 | 关键词: aboutToAppear, aboutToDisappear, @Reusable, aboutToRecycle, aboutToReuse

我见过最离谱的bug是这样的:页面跳转后定时器还在跑,返回时又创建了一个新定时器,两个定时器同时修改同一块数据,UI开始抽风。根源就是对组件生命周期的理解只停留在"aboutToAppear初始化,aboutToDisappear清理"这个表面层次。HarmonyOS NEXT的组件生命周期比大多数前端框架复杂——因为它有@Reusable这个独特的回收复用机制,还有@Entry页面特有的onPageShow/onPageHide/onBackPress。这些回调的执行时序和触发条件,直接决定了你该在哪里初始化、在哪里清理、在哪里重置状态。


最基础的时序:aboutToAppear → build → onPageShow

先从@Entry页面说起。LifecycleDemo的页面组件LifecycleDemo本身标注了@Entry,它的生命周期回调执行顺序如下:

@Entry@Componentstruct LifecycleDemo{aboutToAppear():void{this.addLog('aboutToAppear','Page');}aboutToDisappear():void{this.addLog('aboutToDisappear','Page');}onPageShow():void{this.addLog('onPageShow','Page');}onPageHide():void{this.addLog('onPageHide','Page');}onBackPress():boolean{this.addLog('onBackPress (return true, intercept)','Page');returntrue;}}

实测的执行顺序是:

  1. aboutToAppear— 组件即将出现在组件树中,但还没build
  2. build— 执行UI描述构建
  3. onPageShow— 页面对用户可见

这个顺序很重要。aboutToAppear是做数据初始化的最佳时机,因为此时UI还没构建,你在aboutToAppear里设置的@State值会直接体现在首次渲染中。如果你在onPageShow里才初始化数据,那首次build用的是默认值,onPageShow赋值后又触发一轮重渲染,浪费性能。

onPageShow只在@Entry组件上生效,普通@Component没有这个回调。这一点和Vue的mounted类似——只有页面级组件才有路由相关的生命周期。

离开页面时:

  1. onBackPress— 如果返回true则拦截返回(不执行默认导航)
  2. onPageHide— 页面被隐藏
  3. aboutToDisappear— 组件从树中移除

如果你在onBackPress里返回了true,系统不会自动执行返回导航。你需要自己处理(比如弹确认弹窗,用户确认后手动调router.back())。返回false或不实现onBackPress,则执行默认返回行为。


子组件的回调模式:用回调函数上报日志

LifecycleDemo里有个ChildItem组件,它把生命周期事件通过回调函数传给父组件:

@Componentstruct ChildItem{@StatechildName:string='ChildItem';onLog:(msg:string)=>void=(_msg:string)=>{};aboutToAppear():void{constmsg:string='ChildItem aboutToAppear';hilog.info(0x0000,'LifecycleDemo',msg);this.onLog(msg);}aboutToDisappear():void{constmsg:string='ChildItem aboutToDisappear';hilog.info(0x0000,'LifecycleDemo',msg);this.onLog(msg);}}

父组件使用时传入回调:

if(this.showChild){ChildItem({childName:'ChildItem',onLog:(msg:string)=>{this.addLog(msg,'ChildItem');}}).width('92%')}

这个模式很实用。实际开发中,子组件的生命周期回调里往往需要通知父组件做一些事情(比如取消网络请求、释放共享资源),而ArkUI没有像Vue那样的EventBus机制,回调函数就是最直接的通信方式。

注意onLog的默认值(_msg: string) => {}——这是必须的。ArkTS要求组件的属性必须有初始值,如果只声明onLog: (msg: string) => void不赋默认值,编译直接报错。下划线前缀_msg表示这个参数故意不使用,避免lint警告。

还有一个细节:ChildItem外面包了if (this.showChild)条件渲染。当showChild从true变false时,ChildItem从组件树中移除,aboutToDisappear被调用;从false变true时,新实例创建,aboutToAppear被调用。这不是"显示/隐藏"——是真正的创建/销毁。如果你只想视觉上隐藏,应该用.visibility(Visibility.None)而不是if条件。


@Reusable回收复用机制:ArkUI的性能利器

这是HarmonyOS NEXT组件生命周期里最有意思的部分。看ReusableChild组件:

@Reusable@Componentstruct ReusableChild{@Statelabel:string='';privatecreateCount:number=0;aboutToAppear():void{this.createCount++;constmsg:string='ReusableChild aboutToAppear #'+this.createCount.toString();hilog.info(0x0000,'LifecycleDemo',msg);}aboutToDisappear():void{constmsg:string='ReusableChild aboutToDisappear';hilog.info(0x0000,'LifecycleDemo',msg);}aboutToRecycle():void{constmsg:string='ReusableChild aboutToRecycle -> moved to reuse pool';hilog.info(0x0000,'LifecycleDemo',msg);}aboutToReuse(params:Record<string,string>):void{this.label=params.label??'';constmsg:string='ReusableChild aboutToReuse, label='+this.label;hilog.info(0x0000,'LifecycleDemo',msg);}}

@Reusable的工作流程是这样的:

第一次创建时,和普通组件一样:aboutToAppear → build → 显示。

当组件从树中移除时(比如外层if条件变false),普通组件会调aboutToDisappear然后销毁。但@Reusable组件不同——它先调aboutToRecycle,然后被放入复用池,而不是销毁。

下次需要同类型组件时(if条件变true),框架从复用池里取出已有实例,调aboutToReuse,组件重新挂载到树上。注意:不会再次调aboutToAppear。

父组件里触发回收/复用的代码:

Button(this.showReusable?'Recycle Reusable':'Reuse Reusable').onClick(()=>{this.reusableToggleCount++;this.reusableLabel=String.fromCharCode(65+(this.reusableToggleCount%26));this.showReusable=!this.showReusable;})

点击按钮后,showReusable变false → ReusableChild被回收(aboutToRecycle),再点变true → 从池中取出复用(aboutToReuse),label从参数传入。

aboutToReuse的参数类型是Record<string, string>——一个键值对映射。父组件传给ReusableChild的属性值会以字符串形式打包传进来。比如传了{ label: 'B' },aboutToReuse收到的params就是{ 'label': 'B' }。注意value全是string类型,如果原始数据是number,需要在aboutToReuse里自己做转换。


aboutToDisappear不可靠:app被杀时不保证调用

这是很多人容易忽略的事实。aboutToDisappear只在组件正常从树中移除时触发。如果系统强杀进程(比如内存不足被lmk杀掉、用户从任务管理器划掉应用),aboutToDisappear根本不会执行。

这意味着什么?你不能把关键数据持久化逻辑放在aboutToDisappear里。比如用户编辑了一段文本,你计划在aboutToDisappear里保存到数据库——如果app被强杀,数据就丢了。

正确的做法是:

  • 关键数据在修改时就持久化(每次修改都写偏好设置或数据库)
  • 用AppState回调监听前后台切换,切后台时保存
  • aboutToDisappear只做轻量清理(取消定时器、断开websocket等)

代码里的提示也明确说了:

When the OS forcibly kills the app process, aboutToDisappear is NOT guaranteed. Do NOT rely on it for critical persistence. Use persistent storage or AppState callbacks instead.

这条规则对前端开发者来说可能反直觉——React的componentWillUnmount、Vue的beforeUnmount在SPA场景下基本都能可靠执行。但原生应用的世界不同,进程随时可能被系统回收,你的清理代码可能根本没机会跑。


onBackPress拦截返回的陷阱

onBackPress的返回值决定了系统是否执行默认返回:

onBackPress():boolean{this.addLog('onBackPress (return true, intercept)','Page');returntrue;}

返回true = 拦截,你自己处理返回逻辑。返回false = 不拦截,系统执行默认返回导航。不实现onBackPress等同于返回false。

常见错误:在onBackPress里弹了确认弹窗,但返回了false。结果弹窗刚出来,页面已经跳走了。

正确写法是在弹窗场景下返回true拦截,等用户确认后再手动调router.back():

onBackPress():boolean{if(this.hasUnsavedChanges){this.showConfirmDialog=true;returntrue;}returnfalse;}

另外onBackPress只在@Entry页面生效,普通组件没有这个回调。


生命周期回调的线程安全

ArkUI的所有生命周期回调都在主线程执行,你不需要担心多线程并发问题。但这意味着你不能在回调里做耗时操作——网络请求、大文件读写必须异步处理,否则会阻塞UI渲染。

aboutToAppear是最容易被滥用的回调。有人在里面做同步的数据库查询、同步的文件读取,结果页面加载时白屏好几秒。正确的做法是在aboutToAppear里启动异步任务,数据到了再更新@State:

aboutToAppear():void{this.loadData();}privateasyncloadData():Promise<void>{constresult=awaitfetchDataFromDb();this.dataList=result;}

坑点汇总

  1. aboutToDisappear在进程强杀时不保证调用— 不要在里面做关键数据持久化,改用实时保存或AppState回调。

  2. @Reusable组件复用时不再调aboutToAppear— 从复用池取出的组件只走aboutToReuse,aboutToAppear只在首次创建时执行一次。如果你的初始化逻辑只在aboutToAppear里,复用后状态就是脏的。

  3. aboutToReuse的参数全是string— Record<string, string>意味着number/boolean类型的属性值传过来变成字符串,需要手动转换。

  4. onBackPress必须返回true才能拦截— 返回false或不实现等于不拦截。弹弹窗拦截返回的场景必须return true。

  5. if条件渲染是创建/销毁,不是显示/隐藏— if变false触发aboutToDisappear(或aboutToRecycle),变true创建新实例(或从池复用)。想只隐藏视觉效果用.visibility()。

  6. 生命周期回调里不能做同步耗时操作— 全在主线程,耗时操作会卡UI。


写在最后

生命周期不是"知道有这些回调就行了",而是要精确理解每个回调的触发时机和可靠程度,尤其是@Reusable的复用机制——用好了是性能利器,用错了是bug温床。