鸿蒙应用开发之V2状态管理:@Local、@ObservedV2、@Trace 使用教程
文章目录
一、@State 存在的问题
在 HarmonyOS 状态管理 V1 中,@State 存在一个关键缺陷:@State装饰的变量既可以从父组件传入初始值,也可以在组件内部初始化,这会导致:
-
外部覆盖内部初始值。父组件可以在子组件初始化时传入新值,这会直接覆盖子组件内部 @State定义的初值。例如:
class ComponentInfo { name: string; id: number; message: string; constructor(name:string, id:number, message:string) { this.name = name; this.id = id; this.message = message; } } @Entry @Component struct Index { build() { Column() { // 父组件调用时传入新值,子组件内部定义的初值被覆盖 Child({ componentInfo: new ComponentInfo('Unknown', 0, 'Error') }) } } } @Component struct Child { @State componentInfo: ComponentInfo = new ComponentInfo('Child', 1, 'Hello World'); build() { Column() { Text(JSON.stringify(this.componentInfo)).fontSize(30) } } }运行效果:

-
组件无法感知外部初始化。虽然外部覆盖了内部值,但子组件本身无法感知
componentInfo是从外部传入还是内部定义的。这种不确定性破坏了组件的封装性,不利于状态的管理和维护。 -
状态来源不唯一。一个变量既可能来自组件自身(内部状态),也可能来自外部传入(类似输入参数)。这种语义模糊性导致组件职责不清晰,容易出现数据流混乱的问题。
二、@Local(V2 状态管理)
随着鸿蒙系统的演进,推出了 V2 状态管理。在 V2 中,不再使用 @State,而是改用 @Local 关键字。@Local 是 V2 状态管理的装饰器,功能与 @State 类似,但设计更完善。
将上面的代码中的 @State 改成 @Local:
@ComponentV2
struct Child {
@Local componentInfo: ComponentInfo = new ComponentInfo('Child', 1, 'Hello World');
//...
}
运行效果:

从图中可以看到报错了。外部无法通过父组件给子组件 @Local 修饰的变量传值,确保了数据来源唯一,保证该状态完全由组件自身管理。
2.1 使用 @Local 修饰基本数据类型
@Entry
@ComponentV2
struct Index {
@Local count: number = 0
build() {
Text(this.count + "")
.onClick(() => {
this.count++
console.log(this.count + "")
})
}
}
注意:要想使用V2状态管理,需要把 @Component 改为 @ComponentV2。
2.2 使用 @Local 修饰对象
在 V2 状态管理中,@Local 修饰对象时:只观测变量本身的赋值,即当整个对象被重新赋值时,才会触发 UI 更新。对于对象内部第一层以及更深层属性的直接修改,@Local 默认并不会检测到。
interface Pet {
name: string,
age: number
}
interface Person {
name: string,
age: number,
pet: Pet
}
@Entry
@ComponentV2
struct ObjectLocalDemo {
@Local person: Person = {
name: '张三',
age: 25,
pet: {
name: '旺财',
age: 2
}
}
build() {
Column({ space: 16 }) {
Text(`姓名:${this.person.name}`)
.fontSize(20)
Text(`年龄:${this.person.age}`)
.fontSize(20)
Text(`宠物名:${this.person.pet.name}`)
.fontSize(20)
Text(`宠物年龄:${this.person.pet.age}`)
.fontSize(20)
Button('修改人名')
.onClick(() => {
this.person.name = '李四';
})
Button('修改年龄')
.onClick(() => {
this.person.age = 30;
})
Button('修改宠物名')
.onClick(() => {
this.person.pet.name = '咪咪';
})
Button('修改整个对象')
.onClick(() => {
this.person = {
name: '王五',
age: 35,
pet: {
name: '小黑',
age: 3
}
};
})
}
.width('100%')
.padding(20)
}
}
2.2.1 运行效果分析
点击 修改人名 按钮,从日志中可以看到人名确实修改成功了,但页面上的姓名并没有变化。这是因为 @Local 只观测变量本身的赋值(即整个对象被重新赋值),而 this.person.name = '李四' 是对对象内部属性的修改,@Local 无法检测到。
点击 修改年龄 按钮,同样,年龄虽然修改成功,但页面上的年龄并没有变化,原因同上。
点击 修改宠物名 按钮,从日志中可以看到宠物名确实修改成功了,但页面上的宠物名也没有变化。this.person.pet.name = '咪咪' 是深层属性的直接修改,@Local 同样无法检测到。

点击 修改整个对象 按钮,页面上的所有数据都更新了(整个对象被重新赋值,@Local 能检测到)。
2.2.2 与 @State 的对比
| 对比项 | @State(V1) | @Local(V2) |
|---|---|---|
| 组件装饰器 | @Component |
@ComponentV2 |
| 整个对象重新赋值 | ✅ 触发 UI 更新 | ✅ 触发 UI 更新 |
| 第一层属性直接修改 | ✅ 触发 UI 更新 | ❌ 不触发 UI 更新 |
| 深层嵌套属性直接修改 | ❌ 不触发 UI 更新 | ❌ 不触发 UI 更新 |
| 修改深层属性方式 | 需重新赋值第一层属性 | 需重新赋值整个对象或使用 @ObservedV2 + @Trace |
三、使用 @ObservedV2 + @Trace 实现深层属性观测
从上面的对比表可以看到,@Local 无法检测对象内部属性的直接修改。为了解决这个问题,V2 状态管理提供了 @ObservedV2 和 @Trace 装饰器,它们配合使用可以实现对对象深层属性的精确观测。
3.1 基本概念
@ObservedV2:装饰在 class 上,表示该类是一个可观测的类,其内部被@Trace装饰的属性变化会被追踪。@Trace:装饰在 class 内部的属性上,表示该属性是一个可观测的状态变量,当其值发生变化时,会触发关联的 UI 更新。
注意:
- 类本身必须使用 @ObservedV2装饰,单独使用 @Trace 无效。
- 未被 @Trace装饰的属性无法触发 UI 刷新(即使类被 @ObservedV2装饰)。
3.2 使用 @ObservedV2 + @Trace 改造对象嵌套示例
下面我们用 @ObservedV2 和 @Trace 改造之前的 Person 对象示例,让深层属性修改也能触发 UI 更新:
// 使用 @ObservedV2 装饰类,使其成为可观测类
@ObservedV2
class Pet {
@Trace name: string;
@Trace age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
@ObservedV2
class Person {
@Trace name: string;
@Trace age: number;
@Trace pet: Pet;
constructor(name: string, age: number, pet: Pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
}
@Entry
@ComponentV2
struct ObservedV2Demo {
@Local person: Person = new Person('张三', 25, new Pet('旺财', 2));
build() {
Column({ space: 16 }) {
Text(`姓名:${this.person.name}`)
.fontSize(20)
Text(`年龄:${this.person.age}`)
.fontSize(20)
Text(`宠物名:${this.person.pet.name}`)
.fontSize(20)
Text(`宠物年龄:${this.person.pet.age}`)
.fontSize(20)
Button('修改人名')
.onClick(() => {
this.person.name = '李四';
})
Button('修改年龄')
.onClick(() => {
this.person.age = 30;
})
Button('修改宠物名')
.onClick(() => {
this.person.pet.name = '咪咪';
})
Button('修改宠物年龄')
.onClick(() => {
this.person.pet.age = 3;
})
Button('修改整个对象')
.onClick(() => {
this.person = new Person('王五', 35, new Pet('小黑', 3));
})
}
.width('100%')
.padding(20)
}
}
3.2.1 运行效果分析
运行效果:

点击 修改人名 按钮,页面上的姓名立即更新为"李四"。因为 Person 类被 @ObservedV2 装饰,且 name 属性被 @Trace 装饰,所以 this.person.name = '李四' 这个直接修改能被检测到并触发 UI 更新。
点击 修改年龄 按钮,页面上的年龄立即更新为 30,原因同上。
点击 修改宠物名 按钮,页面上的宠物名立即更新为"咪咪"。注意,这里修改的是 this.person.pet.name,属于深层嵌套属性。因为 Pet 类也被 @ObservedV2 装饰,且 name 属性被 @Trace 装饰,所以深层属性的修改也能被检测到。
点击 修改宠物年龄 按钮,页面上的宠物年龄立即更新为 3,同样能触发 UI 更新。
点击 修改整个对象 按钮,页面上的所有数据都更新了,这与 @Local 的行为一致。
从上面的示例可以看出,@Local 配合@ObservedV2 + @Trace 可以实现对整个对象以及对象深层属性的监听,比使用 @State 配合 @Observed 的要方便很多。
3.2.2 与 @State + @Observed 的对比
| 对比项 | @State + @Observed(V1) | @Local + @ObservedV2 + @Trace(V2) |
|---|---|---|
| 组件装饰器 | @Component |
@ComponentV2 |
| 类装饰器 | @Observed |
@ObservedV2 |
| 属性装饰器 | 无(默认观测所有属性) | @Trace(需手动标记) |
| 整个对象重新赋值 | ✅ 触发 UI 更新 | ✅ 触发 UI 更新 |
| 第一层属性直接修改 | ✅ 触发 UI 更新 | ✅ 触发 UI 更新 |
| 深层嵌套属性直接修改 | ✅ 触发 UI 更新 | ✅ 触发 UI 更新 |
| 性能优化 | 默认观测所有属性,性能开销较大 | 仅观测被 @Trace 标记的属性,性能更优 |
3.2.3 使用建议
- 简单场景:如果只是单个基本类型变量(如
number、string),直接用@Local即可。 - 对象场景:如果对象内部属性需要被直接修改并触发 UI 更新,建议使用
@ObservedV2+@Trace。 - 性能敏感场景:
@ObservedV2+@Trace比 V1 的@Observed性能更好,因为它是按需观测,只追踪被@Trace标记的属性。 - 嵌套对象场景:每一层嵌套的类都需要用
@ObservedV2装饰,且需要被观测的属性都要用@Trace装饰。
四、总结
V2 状态管理提供了更灵活、更精细的状态控制能力:
@Local:用于组件级别的状态变量声明,观测变量本身的赋值。@ObservedV2:装饰 class,使其成为可观测类。@Trace:装饰 class 内部的属性,标记该属性为可观测状态。
三者配合使用,可以实现对对象深层属性的精确观测,同时保持较好的性能表现。在开发鸿蒙应用时,建议优先使用 V2 状态管理方案。
更多推荐

所有评论(0)