ArkTS 组件传对象还是拆属性?我测了帧率,结果和直觉反着来

ArkTS 组件传对象还是拆属性?我测了帧率,结果和直觉反着来

"对象整体传进组件,渲染帧率反而比拆成单个属性高 15% 左右。"

斌哥听完我的结论,放下咖啡杯,表情有点像在看一个说胡话的人。我理解他的反应------因为三天前我也这么看自己。

事情是这样的。上周我在重构 雷达鸭鸿蒙版 的一个政策列表页,每条政策卡片都是一个自定义组件 PolicyCard。一开始我按"最佳实践"把 policy 对象拆成 title、region、date、summary 四个 @Prop 分别传进去,心想这样 ArkTS 的依赖收集应该更精细,不必要的重绘会更少。

结果列表一滑动,肉眼可见的掉帧。

我先是怀疑 LazyForEach 的问题,毕竟列表有 200 多条数据。但把数据源砍到 20 条,卡顿还在。然后怀疑是图片加载,去掉图片后还是卡。那天下午我盯着 Profiler 看了两个多小时,喝了四杯水,上了三趟厕所,还是没想明白问题出在哪。

真正让我锁定方向的,是 DevEco Studio 的 Frame 视图。我切到真机调试,发现滑动过程中平均每帧耗时 12-13ms,时不时蹦到 16ms 以上。而在另一个页面------那个直接把整个对象塞给子组件的页面------同样的数据量,平均帧耗时只有 9-10ms。

等一下,这里我漏说一个前提。ArkTS 的 @Prop 装饰器,官方文档说"用于父子组件的单向同步"。我的理解是:拆得越细,子组件依赖收集的粒度越细,父组件更新一个无关字段时,子组件不会跟着重绘。这逻辑在 Vue 和 React 里都是成立的,我在之前的项目里也确实受益于这种细粒度拆分。

但 ArkTS 的实现逻辑不完全一样。

为了验证,我写了三个版本的 PolicyCard 来做对比测试。数据都是同一批,布局完全一样,唯一的区别就是传参方式。

版本 A:拆属性传(我一开始的写法)

typescript 复制代码
@Component
struct PolicyCardA {
  @Prop title: string;
  @Prop region: string;
  @Prop date: string;
  @Prop summary: string;

  build() {
    Column() {
      Text(this.title).fontSize(16).fontWeight(FontWeight.Bold)
      Text(`${this.region} · ${this.date}`).fontSize(12).fontColor('#999')
      Text(this.summary).fontSize(14).maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
    }
    .padding(12)
    .backgroundColor('#fff')
    .borderRadius(8)
    .width('100%')
  }
}

父组件调用的时候长这样:

typescript 复制代码
PolicyCardA({
  title: item.title,
  region: item.region,
  date: item.date,
  summary: item.summary
})

版本 B:对象整体传

typescript 复制代码
interface PolicyItem {
  title: string;
  region: string;
  date: string;
  summary: string;
}

@Component
struct PolicyCardB {
  @Prop policy: PolicyItem;

  build() {
    Column() {
      Text(this.policy.title).fontSize(16).fontWeight(FontWeight.Bold)
      Text(`${this.policy.region} · ${this.policy.date}`).fontSize(12).fontColor('#999')
      Text(this.policy.summary).fontSize(14).maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
    }
    .padding(12)
    .backgroundColor('#fff')
    .borderRadius(8)
    .width('100%')
  }
}

版本 C:@ObjectLink(我中间尝试的"进阶"写法)

typescript 复制代码
@Observed
class PolicyItemModel {
  title: string = '';
  region: string = '';
  date: string = '';
  summary: string = '';

  constructor(data: PolicyItem) {
    this.title = data.title;
    this.region = data.region;
    this.date = data.date;
    this.summary = data.summary;
  }
}

@Component
struct PolicyCardC {
  @ObjectLink policy: PolicyItemModel;

  build() {
    Column() {
      Text(this.policy.title).fontSize(16).fontWeight(FontWeight.Bold)
      Text(`${this.policy.region} · ${this.policy.date}`).fontSize(12).fontColor('#999')
      Text(this.policy.summary).fontSize(14).maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
    }
    .padding(12)
    .backgroundColor('#fff')
    .borderRadius(8)
    .width('100%')
  }
}

父组件配合版本 C 需要把原始数据包成 PolicyItemModel

typescript 复制代码
this.policyList = rawData.map(item => new PolicyItemModel(item));

测试条件:Mate 60 Pro 真机,HarmonyOS NEXT 5.0,列表 200 条,快速上下滑动,每轮测试 5 秒。

Profiler 数据(五轮测试的中位数):

写法 平均帧耗时 掉帧次数 (>16ms)
A 拆属性 12.8ms 23 次
B 传对象 10.1ms 7 次
C @ObjectLink 11.5ms 14 次

数据摆在这儿,B 最快,A 最慢。我反复测了五轮,中间还重启了一次手机排除缓存干扰,结果稳定得让我有点慌------因为这意味着我过去两周写的那些拆属性传的组件,全都可以优化。

为什么?

我翻了 ArkTS 的编译产物和官方一些内部文档的只言片语,结合自己的理解,大概是这样的:拆属性传的时候,每个 @Prop 都会独立建立一条依赖收集链路。父组件里的数据源稍微动一下------哪怕只是排序变了------四个 @Prop 要分别触发更新判定。ArkTS 的响应式系统在这个阶段的开销,比"直接替换整个对象引用"要大。

传对象的时候,子组件只监听一个 @Prop 引用的变化。数据源重排时,父组件给子组件换的是一个新的对象引用,子组件收到后整件重绘。听起来更"粗暴",但实际帧率更高。因为省去了多路依赖追踪的细粒度比对成本。

@ObjectLink 按理说应该更高效,因为它直接监听对象内部属性的变化,不需要父组件整体刷新。但测试下来它排第二。我猜是因为 @Observed 的代理开销在列表场景下被放大了------200 个卡片就是 200 个代理对象,每个属性访问都要走一层拦截。这个开销在复杂布局里被进一步放大。

说句题外话,鸿蒙的 Profiler 在真机上偶尔会丢帧数据,我测这五轮里有两次开头几秒没采到数据,得重来。这种小毛病挺让人无奈的,但比起之前用 Android Studio 调 HarmonyOS 应用的经历,已经好太多了。

所以我现在写 ArkTS 列表组件,如果子组件不需要反向修改数据,我直接传对象,不再拆了。除非某个属性真的需要单独做细粒度控制------那种情况我会用 @ObjectLink,但不会为了"理论上更好"而主动拆分。

回头一看,这也不是什么高深问题。就是 ArkTS 的响应式实现机制和 Vue、React 不完全一样,我把别的框架的经验直接平移过来,结果翻车了。这种"经验迁移翻车"在我转 ArkTS 的这半年里,已经不是第一次了。

你写 ArkTS 的时候,有过类似的经验迁移翻车吗?


关于我

我叫老三,一个写了十年代码的前端 + 鸿蒙 ArkTS 水手。

目前主业做 Taro 多端项目,业余时间全泡在 AI 自动化和独立开发上------不是因为多热爱加班,而是打心底觉得,程序开发这件事正在被 AI 重构,我不跟上就会被甩下。

这个账号记录的就是我在这条路上的真实经历:踩过的坑、推翻过的方案、以及偶尔值得高兴的小进展。不写教科书,不讲大道理,只分享我自己试过、做过、确认过的东西。

如果你也在写代码,或者也在思考 AI 时代开发者该往哪走------欢迎留言聊聊,一起摸索。

本文遵循 MIT 协议,转载请注明出处。

相关推荐
全栈若城1 小时前
HarmonyOS ArkWeb 系列之文本选中菜单定制:editMenuOptions 深度解析
arkts·arkweb·harmonyos6·editmenuoptions·textmenuitem
木咺吟1 小时前
鸿蒙原生应用开发实战(四):电影详情与评分评价 — 电影清单App
harmonyos
风华圆舞1 小时前
鸿蒙语音播报功能 的 Flutter 侧封装思路
flutter·华为·harmonyos
风华圆舞2 小时前
鸿蒙 + Flutter 下美食探索场景为什么 AI 推荐比传统搜索更自然
flutter·harmonyos·美食
小博测试成长之路2 小时前
行业日报 | 2026年6月12日:Claude新模型、鸿蒙开发者大会与AI工程化加速
人工智能·harmonyos
互联网散修2 小时前
鸿蒙实战:从零实现自定义相机(上)——架构设计与核心实现
数码相机·华为·harmonyos·自定义相机
不羁的木木2 小时前
《HarmonyOS 6.1 新能力实战之智感握姿》第一篇:初识智感握姿——能力与场景全解析
华为·harmonyos
狼哥16862 小时前
《新闻资讯》八、产品定制层实现指南
ui·华为·harmonyos
浮芷.2 小时前
六星光芒阵:HarmonyOS API 24 Canvas 高级绘图实战
科技·华为·开源·harmonyos·鸿蒙