HarmonyOS7 页面生命周期里少做重活:ArkUI/ArkTS 实战拆解

手绘笔记风信息图,主题是 HarmonyOS7 页面生命周期里少做重活。画面中心用一页 ArkUI

前言

在 HarmonyOS7 里写页面,我不太赞成把所有初始化都塞进生命周期。aboutToAppear 很方便,但它不是“页面启动大杂烩”。它适合做轻量初始化、准备默认状态、触发一次可控的数据装载;不适合做大循环、复杂计算、密集日志、反复读写缓存。

我遇到过一种首页:打开时先解析一大段配置,再拼推荐卡片,再计算用户权益,最后还同步生成埋点参数。页面没有明显报错,但冷启动就是慢,返回再进还有偶发卡顿。问题不在 ArkUI,而在生命周期承担了太多不该承担的工作。

我的经验是:生命周期只做“安排工作”,不要在里面“完成所有工作”。真正的计算和状态收敛应该拆到明确的方法里,并且让 UI 先有可展示的骨架。

为什么这个问题经常被写乱

页面生命周期里少做重活 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。

所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。

场景:资讯首页首屏加载

这个案例做一个资讯首页。页面进入时只做三件事:

手绘笔记风流程图,内容是资讯首页首屏加载的实现步骤。流程从 aboutToAppear 开始,只连到

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

手绘笔记风对比图,主题是 HarmonyOS7 页面生命周期的正确分工与错误写法。左栏写推荐做法:a

实现步骤

  1. 定义首页卡片模型,字段只保留 UI 真正需要的部分。
  2. @State 保存加载态、错误文案和卡片列表。
  3. aboutToAppear 里只调用 loadHomeFeed(),不要写复杂逻辑。
  4. loadHomeFeed() 负责模拟数据获取,并统一更新状态。
  5. 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不建议可能阻塞首帧
复杂排序和分组不建议应拆到方法或后置执行
创建定时器谨慎必须配套清理逻辑

写页面时可以这样落地

我一般会先把页面启动流程拆成三层:生命周期、加载方法、数据处理方法。生命周期只负责触发,加载方法负责切换 loadingerrorMessagecards 这些状态,数据处理方法只接收数据并返回结果。

这样写有一个很现实的好处:后面要接真实接口时,不需要重写页面结构。你只要把 remoteCards 换成接口返回,再把异常分支补进 loadHomeFeed(),页面的加载态、失败态和内容态都能继续复用。

还有一点容易被忽略:不是所有变量都要进 @State。只有会影响界面展示的字段才需要响应式更新,像临时排序结果、过滤中间变量,放在普通方法里就够了。状态越少,页面越稳。

写在最后

HarmonyOS7 的页面生命周期并不复杂,复杂的是团队常常把它当万能入口。我的建议很直接:生命周期负责启动流程,业务方法负责完成流程,ArkUI 负责表达状态。这三件事分开后,首页、详情页、搜索页都会更稳。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值