HarmonyOS鸿蒙Next中@State和@Observed装饰器有什么区别?
以下是基于装饰器v1版本来说的

这个说明的比较具体了;
举个例子:
@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) 在子组件中绑定对象,实现属性级别的深度监听。
步骤:
- 定义可观察的数据模型类
使用 [@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;
}
}
- 在父组件中使用 @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)
})
}
}
}
- 在子组件中使用 @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。
步骤:
- 定义 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;
}
}
- 在 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)
})
}
}
}
- 在 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 刷新
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)
推荐使用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)装饰的类实例,才能确保属性级更新生效。



