4-4.【状态管理】@Observed 与 @ObjectLink 的底层区别是什么?大对象频繁更新会带来什么性能问题?如何优化深层状态更新?

3 阅读3分钟

在 ArkUI 的状态管理中,@Observed@ObjectLink 是处理嵌套对象对象数组的核心武器。它们解决了 @State 无法深度观察对象属性变化的局限性。


1. @Observed 与 @ObjectLink 的底层区别

这两者必须成对出现,它们的职责完全不同:

@Observed:类装饰器(观察能力的赋予)

  • 底层行为:它是一个编译插件。当一个类被 @Observed 装饰时,ArkTS 编译器会重写该类的 settergetter
  • 职责:它让类具备了“被观察”的能力。每当你修改类中的成员变量时,它会触发一个内部的 notify 信号。
  • 限制:仅靠 @Observed 是无法刷新 UI 的,它只是一个“信号发射源”。

@ObjectLink:变量装饰器(状态的订阅与代理)

  • 底层行为:它建立了一个指向 @Observed 实例的代理(Proxy)
  • 职责:它充当“信号接收器”。当子组件接收到父组件传来的 @Observed 实例时,@ObjectLink 会将子组件注册为该实例的观察者。
  • 同步机制:它是双向同步的。在子组件内修改属性,直接作用于原始对象实例,并驱动所有链接该实例的组件重绘。

2. 大对象频繁更新带来的性能问题

如果你将一个拥有 100 个字段的大对象定义为 @Observed,并频繁更新其中一个字段,会面临以下压力:

  1. Proxy 拦截开销:所有的属性访问都会经过代理层的拦截逻辑。虽然单次很快,但高频(如动画中每秒 120 次)更新会累积 CPU 耗时。
  2. 冗余重绘(Over-rendering) :如果多个组件 @ObjectLink 了同一个大对象,即使你只改了 A 字段,所有引用了该对象的组件都会被触发“潜在更新检查”。
  3. 深拷贝与内存抖动:在某些复杂的传递场景下,框架为了保证状态一致性可能会产生临时闭包或引用记录,频繁操作大对象会增加垃圾回收(GC)的频率。

3. 如何优化深层状态更新?

针对“深层”和“高频”,可以采用以下三种进阶策略:

A. 状态扁平化 (State Flattening)

核心思想:不要嵌套。将深层属性提取到顶层,或者将大对象拆分为多个独立的小对象。

  • 效果:缩短观察链路,减少受影响的组件范围。

B. 局部化更新 (Localization)

不要在父组件中处理所有逻辑。

  • 做法:将渲染逻辑下放到子组件。让子组件通过 @ObjectLink 只观察它关心的那部分数据。
  • 原理:这样当属性变化时,父组件的 build() 不会重新执行,仅子组件局部刷新。

C. 按需更新与 @Track

在 HarmonyOS 的最新版本中,引入了 @Track 装饰器。

  • 用法:在类成员上使用 @Track
  • 机制:未标记 @Track 的属性变化不会触发 UI 刷新。这实现了属性级别的精准控制,彻底解决了“修改 A 导致引用了 B 的组件也刷新”的问题。

TypeScript

@Observed
class User {
  @Track name: string; // 只有修改 name 才会触发关联 UI 刷新
  age: number;         // 修改 age 不会触发 UI 刷新
}

D. 批量更新 (TaskPool/Emitter)

对于极高频的数据(如传感器、金融秒级行情):

  • 做法:不要直接改状态变量。在后台线程处理好数据,通过 Emitter 定时(如每 100ms)批量同步一次到 UI 状态。

总结

  • @Observed 是“发报机”, @ObjectLink 是“收音机”。
  • 大对象会导致“一人感冒,全家吃药”(冗余刷新),应尽量拆分。
  • 优化首选:使用 @Track 进行属性级过滤,或通过组件拆分实现局部刷新。