鸿蒙基础复盘:生命周期、启动链路、路由跳转与真实需求定位
一、为什么要复盘这一轮
前面已经完成了两个 UI 卡片组件:AI 总结卡片和 AI 问题走马灯卡片。组件层面主要练习了 @ComponentV2、@Param、@Event、@ObservedV2、@Trace、Swiper 和 TouchEvent。
这一轮继续往项目基础走,重点复习:
text
1. 组件生命周期
2. Ability 启动链路
3. 路由跳转与页面传参
4. 真实需求代码定位流程
5. 最小改动原则
6. 数据没出来时的排查顺序
这些内容比单纯写 UI 更接近真实业务开发,因为做需求时不只是把组件画出来,还要知道页面从哪里来、什么时候加载、数据在哪里准备、事件往哪里传。
二、组件生命周期
ArkUI 自定义组件中常用生命周期包括:
text
aboutToAppear()
build()
aboutToDisappear()
1. aboutToAppear()
aboutToAppear() 是自定义组件实例创建后、build() 执行前调用的生命周期。
执行顺序可以理解为:
text
创建组件实例
↓
aboutToAppear()
↓
build()
↓
UI 渲染
它适合做:
text
1. 初始化组件状态
2. 调用 Controller 加载数据
3. 注册监听
4. 启动和当前组件展示相关的逻辑
例如:
ts
aboutToAppear(): void {
this.controller.aboutToAppear()
}
这种写法表示组件即将出现时,把初始化逻辑交给 Controller。
2. build()
build() 是组件 UI 的声明入口,主要负责写 UI 结构和样式。
它适合写:
text
1. Row / Column / Stack 等布局
2. Text / Image / Swiper 等组件
3. 简单条件渲染
4. 样式声明
不适合写:
text
1. 接口请求
2. 复杂业务判断
3. 路由跳转
4. 定时器创建
5. 大量数据处理
3. aboutToDisappear()
aboutToDisappear() 是组件即将销毁、从组件树中移除前调用的生命周期。
它适合清理:
text
1. setInterval
2. setTimeout
3. 页面监听
4. 事件订阅
5. 轮询任务
6. 动画任务
7. 长连接或资源占用
如果组件里有定时器但不清理,可能导致:
text
1. 页面离开后定时器仍然执行
2. 多次进入页面后叠加多个定时器
3. 重复请求接口
4. 重复刷新状态
5. 页面卡顿
6. 内存泄漏
7. 动画速度异常
三、应用启动链路
一个鸿蒙应用不是直接从业务首页开始执行,而是有完整启动链路:
text
module.json5
↓
EntryAbility.ets
↓
onCreate()
↓
onWindowStageCreate()
↓
windowStage.loadContent('pages/MainPage')
↓
MainPage.ets
↓
导航容器 / 路由容器
↓
业务首页
1. module.json5
module.json5 是模块配置文件,不是依赖文件。它通常配置:
text
1. 当前模块信息
2. Ability 信息
3. srcEntry 入口文件
4. pages 配置
5. 权限
6. metadata
系统启动应用时,会根据这里的配置找到 Ability 入口文件。
2. EntryAbility.ets
EntryAbility.ets 是 UIAbility 的实现文件,是当前模块配置的 Ability 启动入口。
它不是普通页面,也不是业务首页,而是应用能力入口。
它通常包含:
text
onCreate()
onWindowStageCreate()
onForeground()
onBackground()
onDestroy()
3. onCreate()
onCreate() 在 UIAbility 创建时执行,适合做 Ability 级别初始化,例如:
text
1. 初始化日志
2. 初始化路由
3. 初始化全局配置
4. 初始化 SDK
5. 读取启动参数
4. onWindowStageCreate()
onWindowStageCreate() 在窗口舞台创建后执行,通常在这里加载根页面:
ts
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/MainPage')
}
5. windowStage.loadContent()
windowStage.loadContent('pages/MainPage') 的作用是加载指定路径下的页面组件作为当前窗口内容。
它本身不一定就是业务首页。
text
loadContent 只负责加载 MainPage。
MainPage 是不是业务首页,要看 MainPage.ets 的具体实现。
如果 MainPage 里面写的是导航容器,那么 MainPage 就负责承接后续路由页面。
四、为什么业务首页不直接写在 EntryAbility 中
真实项目中,通常不会把首页 UI 直接写在 EntryAbility 中,因为它们职责不同:
text
EntryAbility:
负责应用生命周期和窗口加载。
MainPage:
负责承接页面容器或导航容器。
路由系统:
负责页面栈管理、跳转、传参、拦截器、动画等。
业务首页:
负责具体 UI 和业务逻辑。
这样做的好处:
text
1. 启动逻辑和业务 UI 解耦
2. 路由统一管理
3. 方便切换首页、登录页、引导页
4. 方便接入拦截器和页面生命周期监听
5. 多业务模块可以通过路由注册接入
6. EntryAbility 保持简洁
五、路由封装与页面常量
真实项目通常会封装路由跳转方法,例如:
ts
HMUtil.push({
pageUrl: PageConstant.AgentChatPage,
param: {
question: question,
autoSend: true
}
})
封装路由的好处:
text
1. 统一跳转入口
2. 降低业务代码和底层路由库耦合
3. 后续路由库变更时,只改封装层
4. 可以统一加日志、埋点、异常处理、拦截器
5. 调用处代码更简洁
页面路径也不建议直接写字符串:
ts
pageUrl: 'pages/AgentChatPage'
更推荐使用常量:
ts
pageUrl: PageConstant.AgentChatPage
好处是:
text
1. 避免拼写错误
2. 路由地址统一管理
3. 后续改路径只改常量
4. 全局搜索更方便
5. 可以快速知道项目有哪些页面路由
六、页面传参应该由谁组装
页面跳转参数更推荐由 Controller 组装,而不是 UI 组件直接组装。
推荐链路:
text
子组件点击
↓
@Event 抛出必要数据
↓
父组件调用 Controller 方法
↓
Controller 组装路由参数
↓
Controller 调用 HMUtil.push 跳转
示例:
ts
AiQuestionCard({
onQuestionClick: (question: string) => {
this.controller.jumpAgentWithQuestion(question)
}
})
Controller 中:
ts
public jumpAgentWithQuestion(question: string): void {
HMUtil.push({
pageUrl: PageConstant.AgentChatPage,
param: {
question: question,
source: 'ai_question_card',
autoSend: true
}
})
}
这样可以避免 UI 组件和具体业务路由强绑定。
七、目标页面在哪里接收参数
目标页面应该在页面入口层、Controller 初始化阶段或页面级生命周期中统一接收路由参数。
不建议在普通 UI Builder 中到处取参数,因为:
text
1. 参数读取分散,后续不好查
2. build / Builder 应该偏 UI
3. 多处读取容易重复
4. 参数校验、默认值处理应该集中管理
5. 自动行为应该避免重复触发
示例:
ts
aboutToAppear(): void {
this.controller.initRouteParam()
}
Controller 中:
ts
public initRouteParam(): void {
const param = getCurrentParam() as AgentPageParam | undefined
this.viewModel.question = param?.question ?? ''
}
八、点击问题进入 AI 页面并自动发送的完整链路
完整链路可以设计成:
text
1. 用户点击问题气泡。
2. Card 组件触发 onQuestionClick(question)。
3. 父组件接收到 question,调用 Controller。
4. Controller 组装路由参数:
question、source、autoSend。
5. Controller 调用 HMUtil.push 跳转 Agent 页面。
6. Agent 页面在页面级初始化阶段接收路由参数。
7. AgentController 校验参数:
question 是否为空、autoSend 是否为 true、是否已自动发送过。
8. 将 question 填入输入状态或直接构造用户消息。
9. 调用统一发送方法 sendMessage(question)。
10. sendMessage 内部添加用户消息、AI loading、发起请求、更新回复、结束 loading。
这里要特别注意:自动发送要防止重复触发。
ts
private hasAutoSend: boolean = false
public tryAutoSendQuestion(question?: string, autoSend?: boolean): void {
if (this.hasAutoSend) {
return
}
if (!autoSend || !question) {
return
}
this.hasAutoSend = true
this.sendMessage(question)
}
九、拿到真实需求后如何定位代码
拿到一个页面需求后,不要一看到截图就直接改 UI。
推荐流程:
text
1. 看需求截图和文案,找页面上的唯一关键词。
2. 全局搜索文案,定位可能的 UI 文件。
3. 如果搜不到文案,就从已知入口页面开始看点击事件。
4. 顺着 onClick / controller 方法找到跳转逻辑。
5. 根据 pageUrl / 路由常量找到目标页面。
6. 进入目标页面后,看状态分支,比如 HISTORY / SUGGEST / RESULT。
7. 定位真正渲染需求区域的组件。
8. 再看数据来源:ViewModel、Controller、Biz、接口。
截图只能告诉你页面长什么样,不能告诉你代码落点。真实项目可能有多个状态、多个入口、多个复用组件,所以必须先确认路由、状态分支、数据来源和真实组件。
十、最小改动原则
最小改动原则可以理解为:
text
尽量沿用项目原有结构、封装方法和分层方式,只改需求影响范围内的代码。
具体包括:
text
1. 不随便改启动入口
2. 不重复注册已有路由
3. 不绕过项目封装工具类
4. 不把业务逻辑塞进 UI
5. 不为了一个需求大范围重构
6. 优先复用已有 Controller、ViewModel、Biz、路由常量
7. 只改需求相关的最小范围代码
一句话:
text
先顺着项目原有架构加功能,不要为了功能跑通而破坏架构。
十一、什么时候新建组件,什么时候用 @Builder
适合新建组件:
text
1. UI 模块比较完整
2. 有独立业务含义
3. 后续可能复用
4. 代码量比较多
5. 有自己的参数和事件
6. 可以独立维护样式
适合 @Builder:
text
1. 只是当前组件内部的一小段 UI
2. 不需要跨文件复用
3. 只是为了拆分 build 结构
4. 依赖当前组件内部状态较多
一句话:
text
完整模块,新建组件。
组件内部 UI 片段,用 @Builder。
十二、UI 改完后数据没出来怎么排查
推荐排查顺序:
text
1. 先看组件有没有挂载
- 背景出来了吗?
- 按钮出来了吗?
- 临时 Text('测试') 能显示吗?
2. 看父组件有没有传参
- topQuestionList 是否有值?
- bottomQuestionList 是否有值?
3. 看 ViewModel 状态有没有值
- @Trace 字段是否赋值?
- 字段名有没有传错?
4. 看 Controller 有没有执行
- aboutToAppear 是否调用?
- loadData 是否执行?
- initRows 是否执行?
5. 看 Biz / mock / 接口有没有返回
- then 是否进入?
- catch 是否报错?
- 返回数组是否为空?
6. 看数据是否被格式化过滤掉
- 长度限制是不是太严格?
- trim 后是不是空字符串?
- slice 后是不是变空?
7. 看 UI 渲染条件
- if 条件是否挡住?
- ForEach 的数组是不是空?
- key 是否异常?
如果背景和按钮出来了,但问题气泡没出来,说明组件已经挂载,问题更可能在数组为空、父组件没传参或 ViewModel 没赋值。
十三、本轮总结
这一轮把三条线串起来了:
text
组件生命周期:
aboutToAppear / build / aboutToDisappear
应用启动链路:
module.json5 -> EntryAbility -> onCreate -> onWindowStageCreate -> loadContent -> MainPage
真实开发流程:
文案定位 -> 点击事件 -> 路由常量 -> 页面状态 -> 组件 -> ViewModel -> Controller -> Biz
对鸿蒙实习开发来说,真正重要的不只是会写 UI,而是要知道:
text
页面怎么启动
组件什么时候初始化
数据从哪里来
事件往哪里传
跳转参数由谁组装
页面参数在哪里接收
出现问题该从哪一层排查
这些内容掌握以后,做真实业务需求会更稳。
参考链接
-
UIAbility 生命周期
-
自定义组件生命周期
-
自定义组件
-
Navigation / NavDestination
-
Swiper 组件
-
触摸事件