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 ms9 ms+0 MB
IM 消息 500 条1.2 s15 ms+0 MB


3 , 与 @Prop、@Link、@Observed 的区别与协同

  • @Prop:值拷贝,适合原始类型、短字符串。
  • @Link:双向同步,但仍会深拷贝整对象;对象越大越慢。
  • @Observed:监听属性变化,可与 @ObjectLink 组合,实现“引用 + 局部刷新”。
  • @Provide@Consume:作用域是子树,跨模块时层级过深易混乱 , 数据源不易追溯 ; 

总结 : 

  1. 简单值 :@prop -- 单向传递
  2. 小对象 : @Link -- 双向传递
  3. 大对象跨层 : @objectLink
  4. 全局单例 : @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++)
          }
        }
      })
    }
  }
}

关键点 : 

  1. DetailPage 对 item.count的修改自动同步到 RootPage;
  2. 无序列化、无深拷贝;
  3. 仅修改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监听 , 切忌替换个整个对象

Logo

惟楚有才,于斯为盛。欢迎来到长沙!!! 茶颜悦色、臭豆腐、CSDN和你一个都不能少~

更多推荐