在 ArkUI 的状态管理体系中,@Observed 与 @ObjectLink 是处理嵌套对象和对象数组的核心武器。它们解决了 @State 只能监听到对象“第一层”变化的局限。
1. @Observed 与 @ObjectLink 的核心区别
简单来说,这是一对**“母子”**关系:@Observed 作用于类,@ObjectLink 作用于子组件的变量。
| 维度 | @Observed | @ObjectLink |
|---|---|---|
| 装饰对象 | 类 (Class) 。 | 子组件内的变量。 |
| 底层功能 | 赋予类“可被观察”的能力。编译器会给类的属性加上 Getter/Setter 拦截。 | 建立一个“实时连接”。它接收一个被 @Observed 类实例的引用。 |
| 同步方向 | 定义数据源。 | 双向同步。子组件改了属性,父组件和所有链接处同步刷新。 |
| 使用限制 | 只能装饰 Class,不能装饰 Interface 或内置类型。 | 必须配合子组件使用,且参数必须是 @Observed 修饰的实例。 |
2. 大对象监听是否有性能问题?
答案是:如果不加节制地使用,会有显著的性能开销。
性能损耗的来源:
- 内存开销:
@Observed会在编译阶段为类的每一个属性生成代理(Proxy-like)逻辑。如果你的对象有上百个字段,每个实例都会携带大量的拦截器元数据。 - 监听负担: 每一个
@ObjectLink都会向数据源注册一个订阅者。在大对象数组(如 1000 条数据的列表)中,管理这些订阅关系会消耗 CPU 资源。 - 深层遍历: 虽然 ArkTS 优化了 Diff,但如果你在
build()中引用了一个极其复杂的嵌套对象,状态改变时框架仍需解析相关的引用路径。
3. 如何避免深层 Diff?
在 Web 开发(如 React)中,深层 Diff 是通过“浅比较”或“不可变数据(Immutable)”解决的。在 ArkTS 中,我们有更符合原生逻辑的方案:
A. 拆分数据结构(扁平化)
不要设计“祖孙三代”的对象结构。
-
❌
user.post.comment.author.name -
✅ 将
comments抽离为独立的列表,通过 ID 关联。原理: 减少单个
@Observed类的属性数量,让更新只影响最小范围。
B. 使用“按需监听” (Object Ref vs. Property)
如果你只需要显示姓名,不要把整个巨大的 User 对象传给子组件。
- 优化: 在子组件中只接收
name: string。 - 结果: 只有当
name改变时,该子组件才会触发 Diff,对象的其他字段变动不会干扰这个组件。
C. 配合 LazyForEach 缓存
在大对象列表场景下,务必配合 LazyForEach。
LazyForEach的keyGenerator应该基于对象的唯一 ID 而非索引。- 这样当大对象中的某个属性变化时,ArkUI 只会精确 Diff 对应的 Item 节点,而不会扫描整个列表。
4. 最佳实践:什么时候用?
- 对象数组: 列表里的每一项都是一个对象,且点击某一项要修改其内容(如点赞、改名),必须用
@Observed+@ObjectLink。 - 层级嵌套: 只有当你需要观察
obj.subObj.prop时才使用。 - 性能瓶颈: 如果发现滑动卡顿,检查是否在
build()里对@Observed对象做了大量的Array.map或filter等计算逻辑。