在接手公司新一期的企业通讯录模块时,我毫不犹豫地使用了刚掌握的 ArkUI 状态管理 V2 语法。起初以为只是简单的 API 替换,没想到在一次复杂表单交互中,整个页面出现了严重的掉帧与输入延迟。排查许久才意识到,如果没有理清底层数据流的追踪逻辑,V2 同样会引发渲染灾难。
被深度代理拖垮的旧架构
回顾上一版的架构,我们曾大量依赖 @Observed 结合 @ObjectLink。那时为了实现嵌套对象的联动,框架在底层使用了深度代理(Deep Proxy)。只要修改了最外层属性,整个对象树都会被重新遍历。当通讯录里的某个部门对象包含数百个员工节点时,这种深度拦截带来的计算开销直接让 UI 主线程不堪重负。
精准制导的 @Trace 机制
V2 的核心变革在于放弃了粗暴的深度代理。我们不再需要把整个类交给框架去扫描,而是通过 @ObservedV2 和 @Trace 的组合,由开发者明确声明"哪些属性值得被监听"。
typescript
@ObservedV2
class Department {
deptName: string = '研发部';
@Trace staffCount: number = 0;
}
当 staffCount 变化时,框架能够直接定位到绑定该变量的 UI 节点进行刷新,完全没有多余的遍历。这里有个警示:@Trace 必须精确到需要更新的最底层属性,如果只用在外层对象上,内层的频繁变动依然无法触发 UI 的刷新。
断代隔离的混用红线
为了过渡,我试图在一个 V2 的父组件里,塞入一个原本用 V1 编写的子组件。结果测试发现,父组件传下去的变量在子组件里变成了 undefined。
这给我敲响了警示:V1 和 V2 的状态管理底层是完全隔离的两个世界。它们的观察者模式互不相通。如果在项目中决定启用 V2,必须保证整条组件链路,甚至跨越路由的页面,全部迁移到 @ComponentV2、@Local 和 @Param 体系。如果非要跨界,只能借用 AppStorage 进行序列化中转,绝不能直接传引用。
数组操作的监听盲区
在重构部门列表时,为了清空数组,我顺手写了 this.staffList.length = 0。页面没有任何反应。
这也是一个极容易被忽略的警示:V2 的 @Trace 虽然强大,但它依然依赖标准的响应式代理。对于数组的截断或通过索引直接赋值,底层往往无法捕捉。必须老老实实使用 splice 或者重新赋值一个新的数组对象。这也是为什么在重构中,我强制团队在涉及数组更新时,必须通过封装好的工具方法来操作。
找一段你项目中使用了多层嵌套对象的代码。将外层对象的装饰器替换为 @ObservedV2,给需要频繁变动的内部字段加上 @Trace。用性能分析工具跑一下,体会一下消除深度代理后,页面渲染性能的直线拉升。