前言

性能优化不要一上来就找高级工具。很多 HarmonyOS7 页面卡顿,第一眼看代码就能发现:容器套容器、条件分支改根节点、列表项重复造结构。

一张适合技术文章的手绘笔记风信息图,主题是 HarmonyOS 7 复杂首页的渲染层级性能排查。画面

这篇单独聊 复杂首页 这个场景。重点不是堆 API,而是先从页面树、列表项结构和重复 modifier 入手排查性能问题。

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

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

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

场景:运营首页滚动发卡

复杂首页通常是性能问题的集中地:顶部 banner、数据指标、运营位、任务列表、推荐卡片全堆在一个页面里。刚开始数据少时很顺,等接口返回几十张卡片,滚动就开始发卡。这个时候不要第一反应就去找高级优化手段,先看页面树有没有明显浪费。

常见问题很朴素:卡片外面套卡片,标题外面套 Row,图标和文字本来可以一个 Row 解决却加了 Stack。单个列表项多两层不明显,乘以 50 个列表项就会变成真实成本。

渲染层级不是玄学。先把页面树变浅,很多性能问题会直接暴露甚至直接消失。

排查顺序

| 检查项 | 典型现象 | 优先处理方式 |

一张手绘笔记风流程图,内容是 HarmonyOS 7 页面卡顿的排查顺序。流程从“列表项数层级”开始

|—|—|—|
| 容器嵌套 | Column 里只有一个 Row | 合并无意义容器 |
| 根节点变化 | 条件分支切换不同根节点 | 保持列表项结构稳定 |
| 重复样式 | 每张卡片复制一长串 modifier | 收到构建器或组件里 |
| build 计算 | 渲染时拼接、过滤、排序 | 提前整理展示字段 |
| key 不稳定 | 列表刷新闪动 | 使用业务 id |

实操步骤

  1. 先拿一个列表项数层级,标出没有布局价值的容器。
  2. 列表项根节点保持稳定,不要一会儿 Row 一会儿 Column
  3. 把接口数据整理成展示模型,避免在 build() 里做复杂计算。
  4. 重复样式集中到 @Builder 或子组件里,但不要为了封装再套一堆容器。
  5. ForEach 使用业务 id 做 key,避免数据更新后整片重建。

一张手绘对比图,左侧是优化前的 HarmonyOS 7 运营首页列表项,右侧是优化后的扁平化指标卡片

  1. 优化后用真实长列表和低端设备再看一次滚动。

先把页面目标想清楚

在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。

当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。

完整示例:扁平化指标卡片

interface HomeCard {
  id: number
  title: string
  value: string
  trend: string
  desc: string
  warning: boolean
}

@Entry
@Component
struct RenderLevelPage {
  @State cards: HomeCard[] = [
    { id: 1, title: '今日访问', value: '12,430', trend: '+8%', desc: '较昨日提升', warning: false },
    { id: 2, title: '转化订单', value: '346', trend: '+3%', desc: '支付链路稳定', warning: false },
    { id: 3, title: '异常告警', value: '5', trend: '-2%', desc: '仍有接口超时', warning: true },
    { id: 4, title: '退款处理', value: '19', trend: '+1%', desc: '客服待跟进', warning: true }
  ]

  private trendColor(trend: string): string {
    return trend.startsWith('+') ? '#0A7F3F' : '#D92D20'
  }

  @Builder
  MetricCard(card: HomeCard) {
    Row({ space: 12 }) {
      Column({ space: 6 }) {
        Text(card.title)
          .fontSize(13)
          .fontColor('#777777')
        Text(card.value)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        Text(card.desc)
          .fontSize(12)
          .fontColor(card.warning ? '#D92D20' : '#666666')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Text(card.trend)
        .fontSize(14)
        .fontColor(this.trendColor(card.trend))
        .fontWeight(FontWeight.Medium)
    }
    .padding(14)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
  }

  build() {
    Column({ space: 12 }) {
      Text('首页指标')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)

      ForEach(this.cards, (card: HomeCard) => {
        this.MetricCard(card)
      }, (card: HomeCard) => card.id.toString())
    }
    .padding(16)
    .backgroundColor('#F5F7FA')
    .height('100%')
  }
}

把关键代码一段段拆开

MetricCard() 的根节点直接是 Row,没有再额外包一个只负责背景的 Column。背景、圆角、内边距直接挂在根节点上,结构更浅。

trendColor() 是轻量展示逻辑,留在组件里可以接受。如果趋势文案、颜色、图标需要多字段计算,建议在数据进入页面前整理好,比如直接给 trendColor 字段。

列表项内部无论是否告警,结构都一样,只是文案颜色变化。不要为了告警状态切换成另一套根结构,这会让刷新和 diff 更不稳定。

容易踩坑的点

  • 为了统一背景,给每个卡片多套一层容器。
  • 条件渲染时改变列表项根节点,导致刷新闪动。
  • build() 里对数组做排序、过滤、拼接文案。
  • ForEach key 使用 index,接口插入一条数据后整段错位。
  • 把所有样式抽成复杂组件,结果层级比原来更深。

优化建议

性能优化要先看高频区域。首页里静态标题多一层容器问题不大,长列表项多一层容器影响更明显。优先优化会重复出现的列表项、卡片、商品行,而不是纠结页面顶部只出现一次的装饰结构。

如果列表特别长,除了降低层级,还要考虑懒加载、分页和图片占位。渲染层级优化不是银弹,但它是最容易被代码评审发现、也最不容易引入副作用的第一步。

性能优化先盯高频区域

性能排查不要平均用力。页面顶部只出现一次的标题,多一层容器通常不是最紧急的问题;列表项、商品卡片、消息行这种会重复几十次的结构,才是第一优先级。

示例里的 MetricCard() 保持根节点稳定,背景和圆角直接挂在根节点上,避免为了统一样式再套一层没有布局价值的容器。列表项内部结构越稳定,数据刷新时越不容易闪动。

写在最后

优化后一定要用真实数据量验证。只有三四条数据时看不出差异,换成几十条、上百条,再加上低端设备和真实图片,滚动问题才会暴露出来。渲染层级优化不是全部答案,但它通常是最容易先拿到收益的一步。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐