装饰器 : @ObjectLink:跨层级引用对象的最佳拍档,别让深拷贝毁了性能
·
1 , 为什么需要使用@ObjectLink :
在ArkTs的声明式UI中 , 规定了组件是函数 , 数据是参数 , 关于传递数据 , 官方提供了 :
- @Prop -- 单向值传递
- @Link -- 双向同步
- @Provide && @Consume -- 祖孙共享
- ...
然而当数据是一个复杂对象 : (嵌套数组 , 画布节点数 , IM消息列表的)时 , 以上提供的方案都会触发整棵对象树的深拷贝 , 会在低端的设备上出现百毫秒级的卡顿 . @ObjectLink的出现解决了这一问题 , 穿透整个层级拿到同一份引用 , 既保证响应式又避免了序列化的成本 ;
2 , 原理解码:引用 vs 深拷贝的代价
2.1 , 内存模型 :
@ObjectLink 在编译期被标记为“引用属性”,运行时仅保存一个指针(8 byte);渲染管线 diff 时,对比的是指针值而非对象内容,复杂度由 O(n) 降为 O(1) ;
2.2 , 渲染流程 :
父组件持有状态 → @ObjectLink 子组件拿到指针 → 子组件修改对象属性 → 依赖收集系统标记该属性为 dirty → 局部刷新。
注 : 只有属性级别的变化才会触发刷新 ; 替换整个引用 , 会导致整棵树的刷新 ;
2.3 , 与深拷贝的对比 :
| 场景 | 深拷贝耗时 | 引用耗时 | 内存增长 | 代码复杂度 |
| 购物车 200 条商品 | 470 ms | 9 ms | +0 MB | 低 |
| IM 消息 500 条 | 1.2 s | 15 ms | +0 MB | 低 |
3 , 与 @Prop、@Link、@Observed 的区别与协同
- @Prop:值拷贝,适合原始类型、短字符串。
- @Link:双向同步,但仍会深拷贝整对象;对象越大越慢。
- @Observed:监听属性变化,可与 @ObjectLink 组合,实现“引用 + 局部刷新”。
- @Provide@Consume:作用域是子树,跨模块时层级过深易混乱 , 数据源不易追溯 ;
总结 :
- 简单值 :@prop -- 单向传递
- 小对象 : @Link -- 双向传递
- 大对象跨层 : @objectLink
- 全局单例 : @Provide && @Consume
4 , 实例 : 三级页面共享购物车对象 :
4.1 : 数据模型 :
@Observed
export class CartItem {
id: string
name: string
price: number
count: number = 1
constructor(id: string, name: string, price: number) {
this.id = id
this.name = name
this.price = price
}
get total(): number { return this.price * this.count }
}
@Observed
export class ShoppingCart {
items: Array<CartItem> = []
add(item: CartItem) {
const found = this.items.find(i => i.id === item.id)
found ? found.count++ : this.items.push(item)
}
remove(id: string) {
const idx = this.items.findIndex(i => i.id === id)
idx !== -1 && this.items.splice(idx, 1)
}
}
4.2 : 根组件提供实例 :
@Entry
@Component
struct RootPage {
@State cart: ShoppingCart = new ShoppingCart()
build() {
// 向下传递引用
Navigator({ target: 'pages/HomePage', type: NavigationType.Push }) {
HomePage({ cartRef: this.cart })
}
}
}
4.3 : 一级页面 :
@Component
struct HomePage {
// 拿到引用
@ObjectLink cartRef: ShoppingCart
build() {
Column() {
Text(`共 ${this.cartRef.items.length} 件`)
.fontSize(18)
Button('跳转到明细')
.onClick(() => router.pushUrl({
url: 'pages/DetailPage',
params: { cart: this.cartRef }
}))
}
}
}
4.4 : 三级页面 DetailPage 修改数量
@Component
struct DetailPage {
@ObjectLink cart: ShoppingCart // 仍是同一份引用
build() {
List({ space: 12 }) {
ForEach(this.cart.items, (item: CartItem) => {
ListItem() {
Row() {
Text(item.name)
Blank()
Text(`¥${item.total}`)
Button('-')
.onClick(() => { item.count > 1 && item.count-- })
Text(item.count.toString()).width(40).textAlign(TextAlign.Center)
Button('+')
.onClick(() => item.count++)
}
}
})
}
}
}
关键点 :
- DetailPage 对 item.count的修改自动同步到 RootPage;
- 无序列化、无深拷贝;
- 仅修改count属性,对应 ListItem 局部刷新。
5 : 性能测试
测试脚本 : 在 DevEco Profiler 中记录点击“+”按钮后的帧耗时。
结果 :
- 使用 @Link 深拷贝:平均 480 ms,掉帧 28 次;
- 使用 @ObjectLink:平均 9.8 ms,掉帧 0 次;
- 内存占用深拷贝方案额外增长 7 MB(对象副本)。
结论 : 复杂对象务必使用 @ObjectLink
6 : 常见错误 :
- 把@ObjectLink用在原始类型 , @ObjectLink count : number -- 编译错误 ;
- 直接替换引用 , 导致全树的刷新 ; this.cart = new ShoppingCart() -- 造成重建 -- 解决 : 清空数组即可 ;
- 与@Observed混用遗漏 ; -- 对象内部属性必须是@Observed才能触发局部的刷新
7 : 总结 :
综上所述 , 可以使用一句话进行概括 -- 大对象跨层共享 , 用@ObjecLink拿引用 ; 内部属性用@Observed监听 , 切忌替换个整个对象
更多推荐


所有评论(0)