
前言
在 HarmonyOS7 里写页面,我不太赞成把所有初始化都塞进生命周期。aboutToAppear 很方便,但它不是“页面启动大杂烩”。它适合做轻量初始化、准备默认状态、触发一次可控的数据装载;不适合做大循环、复杂计算、密集日志、反复读写缓存。
我遇到过一种首页:打开时先解析一大段配置,再拼推荐卡片,再计算用户权益,最后还同步生成埋点参数。页面没有明显报错,但冷启动就是慢,返回再进还有偶发卡顿。问题不在 ArkUI,而在生命周期承担了太多不该承担的工作。
我的经验是:生命周期只做“安排工作”,不要在里面“完成所有工作”。真正的计算和状态收敛应该拆到明确的方法里,并且让 UI 先有可展示的骨架。
为什么这个问题经常被写乱
页面生命周期里少做重活 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。
所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。
场景:资讯首页首屏加载
这个案例做一个资讯首页。页面进入时只做三件事:

- 准备首屏骨架状态
- 触发轻量数据装载
- 把耗时逻辑放到单独方法里,避免
aboutToAppear变长
| 位置 | 推荐做法 | 不推荐做法 |
|---|---|---|
aboutToAppear | 初始化状态、调用方法 | 写几十行业务判断 |
| 数据加载 | 用方法封装,并设置加载态 | 直接在生命周期里堆数组处理 |
| UI 展示 | 先展示骨架和失败态 | 空白等待所有数据完成 |
| 计算逻辑 | 拆到纯方法,输入输出清楚 | 一边改状态一边拼 UI 字段 |

实现步骤
- 定义首页卡片模型,字段只保留 UI 真正需要的部分。
- 用
@State保存加载态、错误文案和卡片列表。 - 在
aboutToAppear里只调用loadHomeFeed(),不要写复杂逻辑。 loadHomeFeed()负责模拟数据获取,并统一更新状态。build()只根据状态渲染页面,不反向触发业务操作。
这个页面的重点不是“模拟接口”,而是把首屏的责任拆开:生命周期负责启动,加载方法负责收敛状态,纯方法负责数据筛选。这样后面要换成真实网络请求,也不会把 aboutToAppear 改成一大段流程脚本。
先把页面目标想清楚
在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。
当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。
完整代码示例
interface HomeCard {
id: number
title: string
summary: string
tag: string
readMinutes: number
priority: number
}
@Entry
@Component
struct HomeLifecyclePage {
@State loading: boolean = true
@State errorMessage: string = ''
@State cards: HomeCard[] = []
aboutToAppear(): void {
this.loadHomeFeed()
}
private loadHomeFeed(): void {
this.loading = true
this.errorMessage = ''
const remoteCards: HomeCard[] = [
{ id: 1001, title: 'HarmonyOS7 首屏性能排查', summary: '从生命周期、状态更新和列表渲染三个点看卡顿来源。', tag: '性能', readMinutes: 6, priority: 1 },
{ id: 1002, title: 'ArkUI 卡片布局复盘', summary: '把推荐、入口和运营位放在同一个首屏里,层级要克制。', tag: '布局', readMinutes: 4, priority: 2 },
{ id: 1003, title: '状态字段怎么减肥', summary: '只把影响 UI 的数据放进 @State,临时变量留在方法内部。', tag: '状态', readMinutes: 5, priority: 3 }
]
this.cards = this.pickFirstScreenCards(remoteCards)
this.loading = false
}
private pickFirstScreenCards(source: HomeCard[]): HomeCard[] {
const readableCards: HomeCard[] = source.filter((item: HomeCard) => item.readMinutes <= 6)
readableCards.sort((left: HomeCard, right: HomeCard) => left.priority - right.priority)
return readableCards
}
build() {
Column() {
Text('今日推荐')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.width('100%')
if (this.loading) {
Text('正在加载首屏内容...')
.fontSize(15)
.fontColor('#666666')
.margin({ top: 24 })
} else if (this.errorMessage.length > 0) {
Text(this.errorMessage)
.fontSize(15)
.fontColor('#B3261E')
.margin({ top: 24 })
} else {
List({ space: 12 }) {
ForEach(this.cards, (item: HomeCard) => {
ListItem() {
Column({ space: 8 }) {
Row() {
Text(item.tag)
.fontSize(12)
.fontColor('#0A59F7')
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.backgroundColor('#EAF1FF')
.borderRadius(6)
Blank()
Text(`${item.readMinutes} 分钟`)
.fontSize(12)
.fontColor('#777777')
}
.width('100%')
Text(item.title)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.width('100%')
Text(item.summary)
.fontSize(14)
.fontColor('#555555')
.lineHeight(20)
.width('100%')
}
.alignItems(HorizontalAlign.Start)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
}, (item: HomeCard) => item.id.toString())
}
.margin({ top: 18 })
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F5F7FA')
}
}
把关键代码一段段拆开
aboutToAppear() 只保留一行调用。 这不是形式主义,而是给后续维护留边界。以后要加缓存、埋点或灰度策略,也应该落在独立方法里,而不是把生命周期改成“第二个 build”。
loadHomeFeed() 同时管理加载态和数据态。 这里虽然是模拟数据,但结构接近真实项目:先进入加载态,再清空错误,最后一次性给 cards 赋值。这样 UI 不会在多个中间状态之间乱跳。
pickFirstScreenCards() 是纯处理方法。 它不改 @State,只根据传入数组返回结果。纯方法越多,页面越容易测试,也越容易迁移到 ViewModel。
ForEach 使用稳定 id。 首页卡片后续很可能插入运营位或置顶内容,不能用数组下标当 key,否则刷新时容易出现内容复用错位。
生命周期里的轻重边界
下面这张表可以作为写页面时的判断标准:
| 任务 | 能不能放在 aboutToAppear | 原因 |
|---|---|---|
| 设置默认 tab | 可以 | 状态简单,执行快 |
| 触发一次数据加载 | 可以 | 生命周期负责发起动作 |
| 解析大 JSON | 不建议 | 可能阻塞首帧 |
| 复杂排序和分组 | 不建议 | 应拆到方法或后置执行 |
| 创建定时器 | 谨慎 | 必须配套清理逻辑 |
写页面时可以这样落地
我一般会先把页面启动流程拆成三层:生命周期、加载方法、数据处理方法。生命周期只负责触发,加载方法负责切换 loading、errorMessage、cards 这些状态,数据处理方法只接收数据并返回结果。
这样写有一个很现实的好处:后面要接真实接口时,不需要重写页面结构。你只要把 remoteCards 换成接口返回,再把异常分支补进 loadHomeFeed(),页面的加载态、失败态和内容态都能继续复用。
还有一点容易被忽略:不是所有变量都要进 @State。只有会影响界面展示的字段才需要响应式更新,像临时排序结果、过滤中间变量,放在普通方法里就够了。状态越少,页面越稳。
写在最后
HarmonyOS7 的页面生命周期并不复杂,复杂的是团队常常把它当万能入口。我的建议很直接:生命周期负责启动流程,业务方法负责完成流程,ArkUI 负责表达状态。这三件事分开后,首页、详情页、搜索页都会更稳。

965

被折叠的 条评论
为什么被折叠?



