HarmonyOS鸿蒙Next中@State和@Observed装饰器有什么区别?

HarmonyOS鸿蒙Next中@State@Observed装饰器有什么区别? 在写 ArkTS 组件时,经常看到 [@State](/user/State)@Link[@Observed](/user/Observed) 这些装饰器混用,文档解释有点抽象。比如我有一个嵌套对象数组,修改其中某个元素的属性,UI 却没更新。到底该怎么选择响应式装饰器才能保证数据驱动生效?

8 回复

以下是基于装饰器v1版本来说的 cke_178.png

这个说明的比较具体了;

举个例子:

@State是在组件内使用,一般使用场景是:组件内有个基础类型的变量(string text = “hello”)要更新,这个时候用“@State string text = “hello””,这样就能监听到了。

但是有时候我们也需要观察整个对象object。这个时候@State就不适用了。观察这个对象哪个属性更新了就需要用@Observed来装饰这个对象了。

但是又有时候需要更深一级的观察(嵌套对象),这个时候只用@Observed也不行了,要增加一个@ObjectLink,这个就能观察嵌套对象了。@ObjectLink在使用上比较特殊,需要父组件用@Observed装饰对象,再把嵌套对象传到子组件内,在子组件内用@ObjectLink来接受嵌套对象。

至此就完成了一般,普通,特殊三种状态的更新监听了。

更多关于HarmonyOS鸿蒙Next中@State和@Observed装饰器有什么区别?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


建议阅读 ArkUI 状态管理 文档。[@State](/user/State) 用于组件内部状态管理,[@Observed](/user/Observed) 用于标记可观察的类,配合 [@ObjectLink](/user/ObjectLink) 实现嵌套对象的深度监听。

详细说明

核心区别

装饰器 作用对象 使用场景 监听能力
[@State](/user/State) 组件内部变量 管理组件私有状态 只能监听变量本身的赋值变化
[@Observed](/user/Observed) 类定义 标记类为可观察对象 配合 [@ObjectLink](/user/ObjectLink) 实现对象属性深度监听
[@ObjectLink](/user/ObjectLink) 组件属性 绑定被 [@Observed](/user/Observed) 装饰的对象 监听对象内部属性的变化

使用 @State + @Observed + @ObjectLink 组合

说明:这是处理嵌套对象数组的标准方案。[@State](/user/State) 管理数组本身的变化(增删改),[@Observed](/user/Observed) 标记数据类为可观察,[@ObjectLink](/user/ObjectLink) 在子组件中绑定对象,实现属性级别的深度监听。

步骤

  1. 定义可观察的数据模型类

使用 [@Observed](/user/Observed) 装饰类,使其属性变化可被观察:

// Item.ets
[@Observed](/user/Observed)
export class Item {
  name: string = '';
  count: number = 0;
  
  constructor(name: string, count: number) {
    this.name = name;
    this.count = count;
  }
}
  1. 在父组件中使用 @State 管理数组
// ParentComponent.ets
import { Item } from './Item'

@Entry
@Component
struct ParentComponent {
  [@State](/user/State) items: Item[] = [
    new Item('Item 1', 10),
    new Item('Item 2', 20),
    new Item('Item 3', 30)
  ]
  
  /**
   * 修改数组元素的属性
   */
  updateItem(index: number): void {
    // 直接修改对象属性,配合 [@ObjectLink](/user/ObjectLink) 可以触发UI更新
    this.items[index].count += 1
  }
  
  build() {
    Column() {
      ForEach(this.items, (item: Item, index: number) => {
        ItemComponent({ item: item })
      }, (item: Item) => item.name)
      
      Button('修改第一个项')
        .onClick(() => {
          this.updateItem(0)
        })
    }
  }
}
  1. 在子组件中使用 @ObjectLink 绑定对象
// ItemComponent.ets
import { Item } from './Item'

@Component
struct ItemComponent {
  [@ObjectLink](/user/ObjectLink) item: Item
  
  /**
   * 修改对象属性
   */
  updateCount(): void {
    // 直接修改属性,会自动触发UI更新
    this.item.count += 1
  }
  
  build() {
    Row() {
      Text(this.item.name)
        .fontSize(16)
      Text(`数量: ${this.item.count}`)
        .fontSize(14)
      Button('+1')
        .onClick(() => {
          this.updateCount()
        })
    }
    .width('100%')
    .padding(10)
  }
}

使用状态管理 V2(@ObservedV2 + @Trace

说明:如果项目使用状态管理 V2,可以使用 [@ObservedV2](/user/ObservedV2)[@Trace](/user/Trace) 实现更强大的深度监听能力。这种方式支持对象属性的精确观察,性能更优。详细说明请参考:状态管理 V2

步骤

  1. 定义 V2 可观察的数据模型类
// ItemV2.ets
import { ObservedV2, Trace } from '@kit.ArkUI'

[@ObservedV2](/user/ObservedV2)
export class ItemV2 {
  [@Trace](/user/Trace) name: string = '';
  [@Trace](/user/Trace) count: number = 0;
  
  constructor(name: string, count: number) {
    this.name = name;
    this.count = count;
  }
}
  1. 在 V2 组件中使用 @Local 管理数组
// ParentComponentV2.ets
import { ItemV2 } from './ItemV2'

@Entry
@ComponentV2
struct ParentComponentV2 {
  [@Local](/user/Local) items: ItemV2[] = [
    new ItemV2('Item 1', 10),
    new ItemV2('Item 2', 20),
    new ItemV2('Item 3', 30)
  ]
  
  /**
   * 修改数组元素的属性
   */
  updateItem(index: number): void {
    // [@ObservedV2](/user/ObservedV2) + [@Trace](/user/Trace) 支持直接修改属性并触发UI更新
    this.items[index].count += 1
  }
  
  build() {
    Column() {
      ForEach(this.items, (item: ItemV2, index: number) => {
        ItemComponentV2({ item: item })
      }, (item: ItemV2) => item.name)
      
      Button('修改第一个项')
        .onClick(() => {
          this.updateItem(0)
        })
    }
  }
}
  1. 在 V2 子组件中使用 @Param 接收对象
// ItemComponentV2.ets
import { ItemV2 } from './ItemV2'

@ComponentV2
struct ItemComponentV2 {
  [@Param](/user/Param) item: ItemV2 = new ItemV2('', 0)
  
  /**
   * 修改对象属性
   */
  updateCount(): void {
    // 直接修改属性,[@ObservedV2](/user/ObservedV2) + [@Trace](/user/Trace) 会自动触发UI更新
    this.item.count += 1
  }
  
  build() {
    Row() {
      Text(this.item.name)
        .fontSize(16)
      Text(`数量: ${this.item.count}`)
        .fontSize(14)
      Button('+1')
        .onClick(() => {
          this.updateCount()
        })
    }
    .width('100%')
    .padding(10)
  }
}

注意事项

  • @State 的局限性[@State](/user/State) 只能监听变量本身的赋值变化,无法监听对象内部属性的变化。直接修改 [@State](/user/State) 数组元素的属性不会触发 UI 更新
  • @Observed 必须配合 @ObjectLink 使用:单独使用 [@Observed](/user/Observed) 不会自动触发 UI 更新,必须在子组件中使用 [@ObjectLink](/user/ObjectLink) 绑定对象
  • V1 和 V2 不能混用:V1 组件中不能使用 V2 装饰器,V2 组件中不能使用 V1 装饰器。新项目推荐直接使用 V2
  • 数组元素替换 vs 属性修改:如果使用 [@State](/user/State) 管理数组,直接修改数组元素的属性不会触发更新,需要配合 [@Observed](/user/Observed) + [@ObjectLink](/user/ObjectLink) 或使用 V2 的 [@ObservedV2](/user/ObservedV2) + [@Trace](/user/Trace)
  • 性能考虑:V2 的状态管理性能更优,支持精确的属性级更新,避免不必要的 UI 刷新
  • @State:用于组件内部私有状态,值类型(string/number)或简单对象。修改会触发当前组件重绘。
  • @Observed + @ObjectLink:用于复杂嵌套对象(如 class 实例)。需在类定义前加 @Observed,在组件中用 @ObjectLink 引用。当对象内部属性变更时,系统能精准感知并刷新关联 UI。

1. 基础数据类型(包括数组)、对象(非嵌套情况)

非嵌套意思是对象内属性类型都是基础类型。

  • 父组件:**@State** age: number = 10本组件内数据驱动UI,修改此属性值可以驱动对应组件刷新。
  • 子组件:**@Link** age: number父子双向绑定,父/子修改均会通知对方数据更新,进而驱动对应组件刷新。
  • 子组件:**@Prop** age: number父子单向绑定,父修改单向通知子组件数据更新/组件刷新。子组件修改不通知父组件更新。

2. 数组对象、嵌套对象等

对于嵌套情况,被嵌套的对象就要在定义class的时候使用 **@Observed** 修饰。否则对象数组内的 **对象数组[index].属性 = 新值** 发生改变不会被观察到,也就不会驱动刷新UI,因为这个索引指向的对象地址并没有发生变化。

2.1 数组对象

  • 定义class
// 由于是数组包裹对象,所以Item相对于数组来说就是被嵌套的对象
@Observed
class Item {
  // 省略属性
  // constructor() { ... }
}
  • 父组件:和基础类型定义时一样
@State itemList: Item[] = [ new Item(), new Item(), new Item() ]
  • 子组件:如果需要在页面中展示该对象的某个属性是否变化,需要单独写一个子组件,父组件将对象传给子组件,子组件中这个对象属性使用 @ObjectLink 修饰。
@Component
struct ItemComponent {
  @ObjectLink item: Item;
  build() {
    // 展示嵌套对象的属性
    Text(`${this.item.xxx }`)
  }
}

2.2** 嵌套**对象

  • 定义class
class Parent {
  // 一些基础数据类型属性
  // 1. 这些基础数据类型在第1条“对象(非嵌套情况)”下,修改这些属性值UI会更新。
  no: number
  title: string
  // 对象属性(这一步就算是嵌套对象了,需要注意下方注释2说明的例外)
  // 2. 如果直接给对象属性child整体重新赋值为一个新的Child对象,会驱动UI更新。
  // 3. 如果仅给child中的某个属性重新赋值,不能驱动UI更新。
  //    这个和你问题中数组对象不刷新可以当做同一种问题来对待。根源就是这个对象内存地址并没有发生变化,在Parent这一层就被视为数据没有发生变化。
  child: Child
  
  // 构造器
  // constructor() { ... }
}

// Child 是被嵌套对象
@Observed
class Child {
  // 省略属性
  // constructor() { ... }
}

// 如果继续嵌套 那么在UI上也要继续写子孙组件的嵌套,这样页面才会被正确驱动更新
// @Observed
// class GrandChild {
//  // 省略属性
//  // constructor() { ... }
// }
  • 父子组件UI编写参考数组对象。

参考:状态管理(V1)

这个讲起来就比较多了 , 而且确实容易混 , 我给你推荐个神器你可以参考下,能有个大概的思路 ,后期随着项目的开发你就会越来越熟练 , 如有帮助给个采纳谢谢

当然也可以看下官方文档 哈

参考地址
cke_1181.png

推荐使用V2, @ObservedV2搭配@Trace来实现嵌套对象数组的响应式更新

@State装饰器与@Observed装饰器

@State装饰器

用于管理组件内部私有状态,数据变化会触发UI更新。

@Observed装饰器

用于装饰类,使其数据属性变化可被@ObjectLink装饰的变量观察到,主要用于嵌套对象或跨组件共享状态场景。

协同作用

两者协同实现数据与UI同步。

@State@Observed 是 ArkTS 中用于实现数据响应式更新的两个核心装饰器,但它们的职责和使用场景有根本区别。

1. @State:组件内部的状态管理

  • 作用对象:装饰组件内部的私有变量
  • 数据范围:管理的数据属于当前组件,是其内部状态。当被装饰的变量值改变时,会触发当前组件的 UI 重新渲染。
  • 数据类型:可以装饰基本类型(number, string, boolean)以及类、数组或嵌套对象。
  • 关键特性:它建立的是组件内部状态与该组件UI之间的单向绑定。状态变化驱动的是声明该状态的组件自身的更新。

2. @Observed:嵌套对象/类属性的变化观测

  • 作用对象:装饰一个
  • 数据范围:不直接管理状态,而是作为一个“标记”。它用于解决[@State](/user/State)@ObjectLink在装饰复杂嵌套对象或数组时,其内部属性变化无法被UI观测到的问题。
  • 关键特性[@Observed](/user/Observed) 本身不会触发UI更新。它必须与@ObjectLink装饰器配对使用
    • [@Observed](/user/Observed) 装饰,让ArkUI框架能深入观察这个类实例内部属性的变化。
    • @ObjectLink 装饰组件中引用该类的变量,它接收一个被[@Observed](/user/Observed)装饰的类的实例,并建立UI与这个实例内部属性的绑定。

针对你的问题(嵌套对象数组,修改属性UI不更新): 这正是[@Observed](/user/Observed)@ObjectLink要解决的典型场景。[@State](/user/State)可以观察到数组引用本身的改变(如整个数组替换),但默认无法深度观察到数组内某个对象元素的属性变化。

解决方案示例:

// 1. 用 [@Observed](/user/Observed) 装饰你的数据类
[@Observed](/user/Observed)
class User {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

@Entry
@Component
struct ParentComponent {
  // 2. 父组件用 [@State](/user/State) 装饰数组(管理数组引用变化)
  [@State](/user/State) users: User[] = [new User('Alice', 20), new User('Bob', 25)];

  build() {
    Column() {
      // 3. 向子组件传递数组中的单个对象,使用 @ObjectLink
      ForEach(this.users,
        (user: User) => {
          ChildComponent({ user: user }) // 传递单个user对象
        }
      )
      Button('修改Alice年龄')
        .onClick(() => {
          // 4. 修改数组内对象的属性
          this.users[0].age += 1; // 现在这个变化能被 @ObjectLink 观察到并更新UI
        })
    }
  }
}

@Component
struct ChildComponent {
  // 5. 子组件用 @ObjectLink 接收被 [@Observed](/user/Observed) 装饰的类实例
  @ObjectLink user: User; // 建立与User实例内部属性的绑定

  build() {
    Text(`Name: ${this.user.name}, Age: ${this.user.age}`)
  }
}

总结选择策略:

  • [@State](/user/State):用于管理当前组件自身的私有、可变状态。状态变化驱动本组件更新。
  • [@Observed](/user/Observed) + @ObjectLink:当你的数据结构是嵌套对象或对象数组,并且你需要监听其内部属性的变化来更新UI时使用。它们是深度观测的解决方案。
  • @Link(你在问题中也提到):用于建立父子组件之间的双向数据同步,共享同一个数据源。它常与父组件的[@State](/user/State)变量配合使用。

简单来说,[@State](/user/State)管“我自己有什么”,[@Observed](/user/Observed)(配@ObjectLink)管“你(嵌套对象)里面变了要告诉我”。对于嵌套数据,通常需要组合使用:父组件用[@State](/user/State)管理数组,子组件用@ObjectLink接收被[@Observed](/user/Observed)装饰的类实例,才能确保属性级更新生效。

回到顶部