写在前面
如果你写过鸿蒙 ArkUI 应用,大概率遇到过这个场景:
你写了个用户卡片,嵌套对象
user = { name: 'Alice', age: 18 }。点按钮改user.name = 'Bob'------UI 纹丝不动 。 你查文档发现 V1 的@Observed+@ObjectLink要在子组件套着用,父组件改嵌套对象属性根本不触发重绘。 你被迫写this.user = { ...this.user, name: 'Bob' }整对象重赋值------代码丑、性能差、嵌套一深就崩。 你查文档发现鸿蒙 6.1 给了 V2 装饰器@ObservedV2+@Trace------装在 class 上 + �装在成员上,改成员就真触发重绘。你点进去发现 API 一脸懵。
这是 V1「重赋值才更新」和 V2「改属性就深层重绘」的分水岭。鸿蒙 6.1 给的答案是 @ObservedV2 + @Trace ------@ObservedV2 装在 class 上标识可观察、@Trace 装在成员上追踪变化、改成员就真触发 UI 重绘。
本文就用一个真机可跑 的「改嵌套对象属性 → UI 重绘」demo,把 V2 状态追踪从「听名字一脸懵」讲到「下个项目直接抄」。代码托管在 AtomGit,文末有链接,真机实拍截图作证。这是 V2 装饰器系列第一篇。
适合人群:写过鸿蒙应用、被 V1
@Observed嵌套对象更新折磨过的同学。 不适合人群:还在学@State的同学------出门左转看我的入门篇。
一、先讲清楚:@ObservedV2 + @Trace 到底是啥
一句话:@ObservedV2 + @Trace 是鸿蒙 6.1 V2 装饰器,给嵌套对象「改属性就深层重绘」的能力。
V1 的 @Observed + @ObjectLink 要套在子组件里用,父组件改嵌套对象属性不触发重绘------必须整对象重赋值。V2 的 @ObservedV2 + @Trace 直接装在 class 上 + 成员上,改成员就真触发 UI 重绘,不用重赋值。
核心 API 一览:
| 装饰器 | 作用 | 一句话理解 |
|---|---|---|
@ObservedV2 |
装在 class 上 | 「告诉系统这个类可观察」 |
@Trace |
装在成员上 | 「告诉系统追踪这个成员变化」 |
@Local |
装在 struct 成员上 | 「V2 版 @State,组件内状态」 |
@ComponentV2 |
装在 struct 上 | 「V2 组件,启用 V2 装饰器体系」 |
记住这四个,往下看。
二、动手:一个改嵌套对象属性触发重绘的 demo
2.1 定义 @ObservedV2 class + @Trace 成员
typescript
@ObservedV2
class UserModel {
@Trace name: string = 'Alice'
@Trace age: number = 18
}
三个细节:
@ObservedV2装在 class 上------标识这个类可观察,V2 装饰器体系@Trace name/@Trace age装在成员上------追踪这两个成员变化- class 必须顶层定义,不能装在 struct 里(V2 装饰器要求)
2.2 @ComponentV2 struct + @Local 状态
typescript
@Entry
@ComponentV2
struct Index {
@Local user: UserModel = new UserModel()
@Local clickCount: number = 0
@Local logText: string = '(未操作)'
build() {
// ...
}
}
三个细节:
@ComponentV2装在 struct 上------启用 V2 装饰器体系(V1 的@Component不能用 V2 装饰器)@Local user存嵌套对象实例------V2 版的@State,组件内状态@Local clickCount/@Local logText存普通状态------@Local也能存普通类型
2.3 改成员属性 → UI 真重绘
typescript
Button('改 name(@Trace 深层重绘)')
.width('92%').height(44).fontSize(14)
.onClick(() => {
this.user.name = `Alice-${this.clickCount + 1}` // ← 改成员,UI 重绘
this.clickCount++
this.logText = `第 ${this.clickCount} 次:改 name,UI 重绘`
})
这是 V2 最核心的能力 ------改 this.user.name(嵌套对象成员)就真触发 UI 重绘。V1 要写 this.user = { ...this.user, name: 'Bob' } 整对象重赋值才更新,V2 直接改成员就行。
2.4 换整个对象也支持
typescript
Button('换整个 user 对象')
.width('92%').height(44).fontSize(14)
.onClick(() => {
this.user = new UserModel() // ← 换整个对象,UI 重绘
this.clickCount++
this.logText = `第 ${this.clickCount} 次:换整个 user,UI 重绘`
})
V2 同时支持「改成员」和「换整个对象」两种更新方式------按场景选。
三、真机实拍:改成员属性真触发 UI 重绘
我把这个 demo 装到真机上跑(鸿蒙 6.1.1.125, API 24),依次点改 name + 改 age,下面两张都是真机实拍,没有任何 P 图。
初始态:嵌套对象状态(name=Alice, age=18, clickCount=0)+ 三个按钮:

点改 name + 改 age 后:name=Alice-2, age=19, clickCount=3, 日志「第 3 次:改 name,UI 重绘」:

重点看第二张:name 从 Alice 变成 Alice-2、age 从 18 变成 19、clickCount 从 0 变成 3------改嵌套对象成员就真触发了 UI 重绘,不用整对象重赋值 。这是
@ObservedV2+@Trace的真机证明。
四、V2 @ObservedV2 vs V1 @Observed:啥差异
新手最容易纠结的问题:既然 V1 @Observed 那么标准,鸿蒙为啥要造 V2?
| 维度 | V1 @Observed + @ObjectLink |
V2 @ObservedV2 + @Trace |
|---|---|---|
| 装饰器位置 | class 装 @Observed,子组件装 @ObjectLink |
class 装 @ObservedV2,成员装 @Trace |
| 改成员属性 | 不触发重绘(必须整对象重赋值) | 触发重绘(改成员就更新) |
| 嵌套深度 | 一层(多层要套子组件) | 任意层(@Trace 装到任意成员) |
| 配套状态 | @State / @Prop / @Link |
@Local / @Param / @Once |
| 组件装饰 | @Component |
@ComponentV2 |
| 性能 | 重赋值整对象,重 | 改成员就更新,轻 |
一句话决策:新项目鸿蒙 6.1+ 用 V2,改嵌套对象属性一行搞定;V1 项目维护老代码不动。V2 是装饰器体系的未来方向。
五、常见坑(都是血泪)
| 坑 | 症状 | 解法 |
|---|---|---|
@Trace 装在 V1 @Component 里 |
编译报「V2 装饰器只能装在 @ComponentV2」 | struct 改 @ComponentV2 |
| class �装在 struct 里 | 编译报「class 不能嵌套定义」 | class 顶层定义,struct 外面 |
@ObservedV2 忘装 |
改成员不触发重绘 | class 上必须装 @ObservedV2 |
@Trace 忘装 |
改那个成员不重绘 | 每个要追踪的成员都要装 @Trace |
@Local 用在 V1 @Component |
编译报「@Local 是 V2 装饰器」 | struct 改 @ComponentV2 + @Local |
| V1 V2 混用 | 编译报「不能多个状态管理装饰器」 | 一个 struct 只用 V1 或只用 V2,别混 |
| 嵌套对象数组改 push | 不触发重绘 | 数组用 @Trace + 整数组重赋值,或用 V2 的 @Trace 数组 |
六、V1/V2 双轨体系
鸿蒙 6.1 API 23+ 装饰器体系 V1/V2 双轨:
| 体系 | 组件装饰 | 状态装饰 | 嵌套对象 | 跨层传递 |
|---|---|---|---|---|
| V1 | @Component |
@State / @Prop / @Link |
@Observed + @ObjectLink |
@Provide + @Consume |
| V2 | @ComponentV2 |
@Local / @Param / @Once |
@ObservedV2 + @Trace |
@Provider + @Consumer |
V1 和 V2 不能混用------一个 struct 只用 V1 或只用 V2。新项目优先用 V2 避坑。
七、完整代码仓库
本文所有代码都已托管到 AtomGit,欢迎 clone、提 issue、点 star:
🔗 仓库地址 :atomgit.com/JaneConan/a...
仓库包含:
- 完整的「@ObservedV2 + @Trace 改嵌套对象属性触发重绘」demo 工程
Index.ets主页面(@ObservedV2class +@Trace成员 +@ComponentV2struct +@Local状态)- 改成员 / 换整个对象两种更新姿势
- 可直接用 DevEco Studio 打开运行(真机装普通应用必能跑)
八、下一步该学什么?
跑通这个 demo 之后,你的 V2 嵌套对象状态追踪就入门了。这是 V2 装饰器系列第一篇,后续按这个顺序往下:
AppStorageV2(下一篇):V2 应用级状态,全局存取@Param+@Once:V2 父子传参,替代 V1@Prop@Provider+@Consumer:V2 跨层传递,替代 V1@Provide+@Consume@Monitor:V2 监听变化,替代 V1@Watch@Computed:V2 计算属性,自动派生
写在最后
@ObservedV2 + @Trace 的本质,是**「鸿蒙 6.1 给嵌套对象改属性就深层重绘的能力」**------不是 V1 的「重赋值才更新」,是 V2 专门给精细追踪的原生机制。代价是 class 上多装一个 @ObservedV2、成员上多装一个 @Trace。
一旦你开始用 V2 思维写嵌套对象状态,你会发现大部分「用户卡片改 name」「购物车改数量」「表单改字段」的需求,都是 @ObservedV2 + @Trace 的自然结果。代码量比 V1 重赋值少一半,精细度高一倍。
代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 跑起来,亲手点改 name 改 age 感受下深层重绘。
跑通了,回来评论区打个「1」,我看看有多少人真的动手了。🚀
作者:JaneConan 仓库:atomgit.com/JaneConan/a... 协议:Apache-2.0,随便用,别告我