做过复杂业务页面的开发者,大概都被状态不更新折磨过。页面嵌套了三层对象,你修改了最底层的属性,界面上却毫无反应,最后只能用各种临时变量去强行刷新。为了解决这个问题,HarmonyOS 引入了全新的状态管理 V2 版本。但这并不意味着你可以无脑升级,如果底层数据流没理清,V2 同样会给你带来灾难。
被 V1 深度代理拖垮的性能
先复盘一下我们在老项目中遇到的痛点。在 V1 体系中,要让一个类对象的属性变化被监听,必须使用 @Observed 结合 @ObjectLink。
typescript
@Observed
class UserProfile {
name: string = '张三';
settings: { theme: string } = { theme: 'dark' };
}
当你把这个对象传递给子组件时,只要修改了 name,UI 确实会刷新。但 @Observed 的底层机制是深度代理(Deep Proxy),它会递归遍历对象的所有层级。如果你的 settings 里还有更复杂的嵌套,或者是一个几万条数据的数组,整个代理过程会产生巨大的内存开销,导致页面滑动明显掉帧。
更致命的是,这种深度代理在面对动态追加的属性时往往会失效。新加的属性没有在初始化时被拦截,你改得再多,UI 依然稳如泰山。为了解决这些问题,我们开启了向 V2 的重构之路。
精准追踪:@ObservedV2 与 @Trace 的威力
V2 的核心改变是引入了更细粒度的追踪机制。我们不再需要把整个类丢给框架去深层遍历,而是由开发者明确告诉框架:"你只需要盯着这个属性"。
重构后的类定义变成了这样:
typescript
@ObservedV2
class UserProfile {
@Trace name: string = '张三';
// 只有被 @Trace 修饰的变量,才会触发 UI 刷新
@Trace settings: { theme: string } = { theme: 'dark' };
}
在组件层面,父组件用 @Local 替代 @State,子组件用 @Param 替代 @Prop。
这里有一个极其关键的警示:@Trace 必须配合 @ObservedV2 使用,且只能修饰类的属性,不能修饰整个类或方法。另外,如果是多层嵌套的对象,你必须在需要监听的每一层属性上都加上 @Trace。如果只加在外层,内层的变化依然会被漏掉。
千万别跨版本混用
刚开始重构时,为了赶进度,我留了几个老页面用的是 V1 语法,只把新增的模块切到了 V2。结果测试时发现,V1 页面跳转到 V2 页面,参数传过去变成了 undefined;V2 页面里的输入框,修改后返回 V1 页面,状态也完全丢失。
这是一个非常隐蔽的警示:V1 和 V2 的状态管理体系在底层是完全隔离的 。它们的观察者模式、代理机制、生命周期都不互通。如果你决定重构,就必须将整条数据链路上的组件全部迁移。哪怕中间跨越了路由跳转,也必须用 AppStorage 这种系统级的桥接方案,将数据序列化后传递,绝不能在 V1 的 @State 和 V2 的 @Local 之间直接引用。
复杂数据的更新边界
在 V2 里用 @Trace 修饰数组,有一个极易踩中的陷阱。如果你像以前一样,用 arr.length = 0 或者 arr[index] = newItem 这种方式去改变数组,框架往往会监听不到。
正确的做法是使用数组自带的方法,比如 push、splice 等,或者直接重新赋值整个数组。对于深层嵌套的字典对象,要避免使用 delete 操作符去删属性。为了保证状态的绝对可靠,如果是极其复杂的业务数据,建议搭配 @Event 事件回调,手动触发一次局部刷新,这比依赖底层代理更稳妥。
留给你的实践任务
找一个你现有的个人资料页面,把里面的类定义从 @Observed 改成 @ObservedV2,并在需要修改的字段上加上 @Trace。把组件装饰器换成 @ComponentV2,将 @State 换成 @Local。跑一遍修改密码和修改头像的流程,体验一下精准刷新带来的丝滑感。