HarmonyOS鸿蒙Next中状态变量V1里的@ObjectLink有什么用吗

HarmonyOS鸿蒙Next中状态变量V1里的@ObjectLink有什么用吗 状态变量V1里的@ObjectLink有什么用吗,实测不使用@Observed/@ObjectLink,只使用@Observed/@state子组件更改对象属性值也可以引起ui渲染:

父元素将对象的内存地址传给了子元素,是是深拷贝,所以子元素里改对象的某个属性父元素也会改变,所以我有疑问:@ObjectLink有啥用??有哪位大佬能帮我写一个单用@Observed实现不了,必须结合@ObjectLink的例子

// 1. 类加 [@Observed](/user/Observed)
[@Observed](/user/Observed)
class User {
  name: string=''

  constructor(name: string) {
    this.name = name
  }

}

// 父
@Entry
@Component
struct Parent {
  // 2. 必须用 @State 装 Observed 对象
  @State user: User = new User('tom')

  build() {
   Column(){
     Text('父'+this.user.name)
     Button().onClick(()=>{
       this.user.name='A'
     })
     // 传给子
     Child({ u: this.user })

   }
  }
}

// 子
@Component
struct Child {
  @State u: User=new User('jeery')
  build() {
    Column(){
      Text('子'+this.u.name)
      Button().onClick(()=>{
        this.u.name='B'
      })
    }
  }
}

更多关于HarmonyOS鸿蒙Next中状态变量V1里的@ObjectLink有什么用吗的实战教程也可以访问 https://www.itying.com/category-93-b0.html

10 回复

有用,而且你这个实测现象不代表 @ObjectLink 没价值,核心是你把两件事混在一起了:

  1. “对象是不是同一个引用”;
  2. “父子状态语义是不是双向同步、能不能稳定追踪”。

你这段代码里,子组件的 @State u 确实可能拿到和父组件同一个 User 实例,所以子里改 this.u.name,父里也会变。这不是深拷贝,恰恰更接近“同一个引用对象被两个组件拿着”。

但官方对 @State 的定义很明确:它只是“用父组件传入的值做初始化”,后续不和父组件同步。也就是说,它能跑,不等于它的语义就是父子双向绑定。

所以 @ObjectLink 的作用,不是单纯“让对象引用共享”,而是:

  1. 明确建立父子双向同步关系;
  2. 让子组件成为这个被观察对象的正式依赖方;
  3. 子组件不能擅自把整个对象换掉,只能改属性,避免把同步链打断;
  4. 处理嵌套对象时,补上 V1 只观察第一层的短板。

示例代码:

[@Observed](/user/Observed)
class User {
  name: string

  constructor(name: string) {
    this.name = name
  }
}

@Entry
@Component
struct Parent {
  [@State](/user/State) user: User = new User('tom')

  build() {
    Column() {
      Text('父: ' + this.user.name)

      Button('父组件修改 name')
        .onClick(() => {
          this.user.name = 'A'
        })

      Button('父组件整体替换 user')
        .onClick(() => {
          this.user = new User('lucy')
        })

      Child({ u: this.user })
    }
  }
}

@Component
struct Child {
  [@ObjectLink](/user/ObjectLink) u: User

  build() {
    Column() {
      Text('子: ' + this.u.name)

      Button('子组件修改 name')
        .onClick(() => {
          this.u.name = 'B'
        })

      // 这里不允许 this.u = new User(...)
      // [@ObjectLink](/user/ObjectLink) 不能整体赋值,只能改属性
    }
  }
}
  1. 子改 this.u.name,父会同步看到;
  2. 父整体替换 this.user = new User(‘lucy’),子会在父刷新后重新拿到新的数据源;
  3. 子不能偷偷换整对象,避免同步链断掉。

总结: 你现在的例子能刷新,是因为“共享了同一个被 @Observed 代理过的对象实例”,不是因为 @State 替代了 @ObjectLink@ObjectLink 真正的价值在于“父子双向同步语义 + 禁止子组件整体换对象 + 嵌套对象单独建观察链”。

更多关于HarmonyOS鸿蒙Next中状态变量V1里的@ObjectLink有什么用吗的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


你这个现象容易让人误解:把对象引用传给子组件后,子组件确实可能改到同一个对象,但这不等于已经建立了可靠的父子状态同步。@ObjectLink 的价值是配合 @Observed,把子组件注册到被观察对象的依赖链里,尤其用于对象数组、嵌套对象、列表项子属性变化这些场景。单层字段有时看起来只靠 @State 也能刷新;但像 user.profile.addr.city 这种深层属性,父组件直接 Text 读取通常不会因为深层字段变化而刷新。实践上建议:父组件 @State 持有外层对象/数组,ForEach 用稳定 id;子组件用 @ObjectLink 接收 @Observed 的数组项或内层对象;如果父组件也要显示深层字段,要么整体替换外层对象,要么把显示深层字段的 UI 也拆到接收 @ObjectLink 的子组件里。

跑了一下demo,整理了一些资料,看看:

在 HarmonyOS ArkUI 里,你的观察是对的:只要类被 @Observed 装饰,且父组件用 [@State](/user/State) 持有对象实例,那么子组件无论用 [@State](/user/State) 还是 [@ObjectLink](/user/ObjectLink) 接收,修改该对象的属性,都能触发子组件的 UI 刷新,并且父组件的对象属性也确实被改变了。

但这不代表 [@ObjectLink](/user/ObjectLink) 没用,它的核心价值在于 “同步引用”,而非仅仅“同步属性变化”。
下面用一个 必须使用 [@ObjectLink](/user/ObjectLink) 否则无法正确渲染 的场景,你马上就能看懂区别。


场景:父组件整体替换对象,子组件需要自动跟随

  • [@State](/user/State) 接收对象时,子组件存的是初始传入时那份引用的快照
    当父组件把整个 user 换成另一个新对象时,子组件的 [@State](/user/State) u 不会更新,UI 停留在旧对象上。
  • [@ObjectLink](/user/ObjectLink) 接收对象时,子组件与父组件共享同一引用链路
    父组件一替换对象,子组件会立刻同步新引用并刷新 UI。

❌ 错误示例:只用 @State,父组件替换对象后,子组件不更新

@Observed
class User {
  name: string = '';
  constructor(name: string) {
    this.name = name;
  }
}

@Entry
@Component
struct Parent {
  [@State](/user/State) user: User = new User('tom');

  build() {
    Column({ space: 10 }) {
      Text(`父组件:${this.user.name}`).fontSize(20)

      Button('父组件替换整个对象为 Jerry')
        .onClick(() => {
          // 整体替换成一个新对象
          this.user = new User('Jerry');
        })

      // 子组件用 [@State](/user/State) 接收
      ChildByState({ u: this.user })
    }
    .padding(20)
  }
}

@Component
struct ChildByState {
  [@State](/user/State) u: User = new User('default'); // 这是本地状态,只初始化一次

  build() {
    Column() {
      Text(`子组件([@State](/user/State)):${this.u.name}`).fontSize(18).fontColor(Color.Red)
    }
  }
}

cke_756.png

运行结果:点击父组件按钮替换对象后,父组件显示 Jerry,但子组件仍然显示 tom
因为 ChildByState[@State](/user/State) u 只在创建时被赋值一次,后续父组件传入的新引用对它没影响。

✅ 正确示例:使用 @ObjectLink,子组件自动跟随引用变化

@Observed
class User {
  name: string = '';
  constructor(name: string) {
    this.name = name;
  }
}

@Entry
@Component
struct Parent {
  [@State](/user/State) user: User = new User('tom');

  build() {
    Column({ space: 10 }) {
      Text(`父组件:${this.user.name}`).fontSize(20)

      Button('父组件替换整个对象为 Jerry')
        .onClick(() => {
          // 整体替换
          this.user = new User('Jerry');
        })

      // 子组件用 [@ObjectLink](/user/ObjectLink) 接收
      ChildByObjectLink({ u: this.user })
    }
    .padding(20)
  }
}

@Component
struct ChildByObjectLink {
  // 关键:[@ObjectLink](/user/ObjectLink) 必须与 @Observed 搭配,且不能设置初始值
  [@ObjectLink](/user/ObjectLink) u: User;

  build() {
    Column() {
      Text(`子组件([@ObjectLink](/user/ObjectLink)):${this.u.name}`).fontSize(18).fontColor(Color.Green)
    }
  }
}

cke_4313.png

运行结果:点击按钮后,父组件和子组件同时tom 变为 Jerry
因为 [@ObjectLink](/user/ObjectLink) 建立的是一条引用同步通道,而不是一次性拷贝。


总结:@ObjectLink 究竟有什么用?

场景 @State 接收对象 @ObjectLink 接收对象
修改对象属性 ✅ 双方都能刷新 ✅ 双方都能刷新
父组件替换整个对象 ❌ 子组件不更新 ✅ 子组件自动同步新引用
对象多层嵌套且需要局部刷新 可能重建不必要的渲染 精确跟踪被修改的路径,性能更好
避免不必要的深拷贝 无深拷贝,但引用断连 无拷贝,且引用始终保持一致

所以,[@ObjectLink](/user/ObjectLink) 不可或缺的场景就是:父组件会重新赋值整个对象,且要求子组件实时响应
在你原来的代码里,恰好只测试了“修改属性”,而没测试“替换整个对象”,才会觉得它没用。实际复杂应用中,这种引用级同步非常重要。

官方给的例子很清晰的,正例、反例、各种类型情景,都举了。《使用场景》
@State@ObjectLink修饰对象数组举个例子。

  1. 两者变化都可影响本组件;修饰数组时,都不能观察到数组项对象属性的变化。
  2. @ObjectLink还可和父组件和初始化自己关联的状态变量双向影响。
    示例:
//被观察者
@Observed
class Fruits{
  count:number = 0;
  constructor(count:number) {
    this.count = count;
  }
}

@Component
struct Child{
  [@ObjectLink](/user/ObjectLink) fruitArr:Fruits[];//变化可影响本组件和初始化本变量关联的状态变量
  [@State](/user/State) fruits:Fruits[] = [new Fruits(0)];//变化只能影响本组件
  build() {
    Column({space:10}){
      Text(`子 State的:${this.fruits[0].count}`)
      Text(`子 ObjectLink的:${this.fruitArr[0].count}`)

      Button('子State 数组整体赋值')
        .onClick((event: ClickEvent) => {
          this.fruits = [new Fruits(Math.random()*100)];
        })
      Button('子State 数组项对象赋值')
        .onClick((event: ClickEvent) => {
          this.fruits[0] = new Fruits(Math.random()*100);
        })
      Button('子State 数组项对象属性赋值')
        .onClick((event: ClickEvent) => {
          this.fruits[0].count = Math.random()*100;
        })

      Button('子ObjectLink 整体赋值【错误】')
        .onClick((event: ClickEvent) => {
          //错误,ObjectLink不允许整体赋值,闪退以后不干掉后台重新打开,偶尔状态管理会出现异常
          this.fruitArr = [new Fruits(Math.random()*100)];
        })
      Button('子ObjectLink 数组项对象赋值')
        .onClick((event: ClickEvent) => {
          this.fruitArr[0] = new Fruits(Math.random()*100);
        })
      Button('子ObjectLink 数组项对象属性赋值')
        .onClick((event: ClickEvent) => {
          this.fruitArr[0].count = Math.random()*50;
        })
    }.width('100%')
    .alignItems(HorizontalAlign.Center)
  }
}

@Entry
@Component
struct Parent {
  [@State](/user/State) fArr:Fruits[] = [new Fruits(9)];
  build() {
    Column({space:10}){
      Text(`父 State的:${this.fArr[0].count}`)
      Button('父State 整体赋值')
        .onClick((event: ClickEvent) => {
          this.fArr = [new Fruits(Math.random()*100)]
        })
      Button('父State 数组项对象赋值')
        .onClick((event: ClickEvent) => {
          this.fArr[0] = new Fruits(Math.random()*100)
        })
      Button('父State 数组项对象属性赋值')
        .onClick((event: ClickEvent) => {
          this.fArr[0].count = Math.random()*100
        })
      Divider()
      Child({fruitArr:this.fArr, fruits:this.fArr});

    }.width('100%')
    .alignItems(HorizontalAlign.Center)
  }
}

状态管理在响应式设计里是很重要的一环,官方在这一块给了大量详细的解释和示例。可细读。

用你的代码传一个数组对象,看看效果,然后再用下面类,修改一下你的代码,再运行看看效果。

将Array变为ObseredArray,使用Observed监听。
//xxx.ets
@Observed
export class ObservedArray<T> extends Array<T> {
    constructor(args?: T[]) {
      if (args instanceof Array) {
        super(...args);
      } else {
        super();
      }
    }
}

感谢您的回复,不过我看这个案例好像没有体现父组件或者子组件两者一项更改,两处组件的ui都发生,

你是

@State cousin: Cousin = new Cousin(10, 20, 30);

但你传给子组件的是child对象而不是cousin

ViewChild({ child: this.cousin.child })

我在父组件加了一行代码,但子组件点击button按钮后ui无法同步更新:

Text(`childId:  ${this.cousin.child.childId}`)

cke_7159.png

@Observed/@ObjectLink适用于观察嵌套对象(对象的属性是对象)属性的变化,如比如二维数组、对象数组、嵌套类场景。

class Parent {
  public parentId: number;
  constructor(parentId: number) {
    this.parentId = parentId;
  }
  getParentId(): number {
    return this.parentId;
  }
  setParentId(parentId: number): void {
    this.parentId = parentId;
  }
}

[@Observed](/user/Observed)
class Child {
  public childId: number;
  constructor(childId: number) {
    this.childId = childId;
  }
  getChildId(): number {
    return this.childId;
  }
  setChildId(childId: number): void {
    this.childId = childId;
  }
}

class Cousin extends Parent {
  public cousinId: number = 47;
  public child: Child;
  constructor(parentId: number, cousinId: number, childId: number) {
    super(parentId);
    this.cousinId = cousinId;
    this.child = new Child(childId);
  }
  getCousinId(): number {
    return this.cousinId;
  }
  setCousinId(cousinId: number): void {
    this.cousinId = cousinId;
  }
  getChild(): number {
    return this.child.getChildId();
  }
  setChild(childId: number): void {
    this.child.setChildId(childId);
  }
}

@Component
struct ViewChild {
  [@ObjectLink](/user/ObjectLink) child: Child;
  build() {
    Column({ space: 10 }) {
      Text(`childId: ${this.child.getChildId()}`)
      Button('Change childId')
        .onClick(() => {
          this.child.setChildId(this.child.getChildId() + 1);
        })
    }
  }
}

@Entry
@Component
struct MyView {
  @State cousin: Cousin = new Cousin(10, 20, 30);
  build() {
    Column({ space: 10 }) {
      Text(`parentId: ${this.cousin.parentId}`)
      Button('Change Parent.parentId')
        .onClick(() => {
          this.cousin.parentId += 1;
        })
      Text(`cousinId: ${this.cousin.cousinId}`)
      Button('Change Cousin.cousinId')
        .onClick(() => {
          this.cousin.cousinId += 1;
        })
      ViewChild({ child: this.cousin.child }) // Text(`childId: ${this.cousin.child.childId}`)的替代写法
      Button('Change Cousin.Child.childId')
        .onClick(() => {
          this.cousin.child.childId += 1;
        })
    }
  }
}

如果Child 使用@ObjectLink,Child必须传参数,且不能初始化变量;

如果Child 使用@State,Child参数可传可不传。

@ObjectLink 用于在父子组件间共享对象类型状态变量,子组件通过该装饰器直接引用父组件对象,实现属性级双向同步。它不创建副本,父组件对象属性变化时会触发子组件更新。适用于需要子组件修改对象内部属性并同步回父组件的场景。

@Observed
class User {
  name: string = ''
  constructor(name: string) { this.name = name }
}

@Entry
@Component
struct Parent {
  @State user: User = new User('Tom')

  build() {
    Column() {
      Text('父: ' + this.user.name)
      Button('父改为Jerry')
        .onClick(() => {
          // 整体替换对象
          this.user = new User('Jerry')
        })
      // 传入子组件,子组件需同步此替换
      Child({ u: this.user })
    }
  }
}

@Component
struct Child {
  // 用 @ObjectLink 接收,不能初始化,且必须用 $ 语法
  @ObjectLink u: User

  build() {
    Column() {
      Text('子: ' + this.u.name)
      Button('子改名')
        .onClick(() => {
          this.u.name = 'Spike' // 同步回父
        })
    }
  }
}
回到顶部