在 ArkUI 的开发中,最让开发者头疼的往往不是语法错误,而是“代码运行了,但 UI 纹理不动”。这些“隐性 Bug”大多源于对 ArkTS 状态监听机制(Proxy/Wrapper) 的理解偏差。
1. 为什么修改对象属性 UI 不更新?
这是最典型的“观察失败”场景。
根本原因:深度观察限制
@State、@Prop、@Link 等装饰器,默认只能观察到 第一层 变量的引用变化或基本类型的赋值。
- 无效操作: 如果你定义了
user: User = new User('Alice'),执行this.user.name = 'Bob'。对于框架来说,user这个对象的**内存地址(引用)**没有变,它感知不到内部属性的微小挪动。
解决方案:
-
使用
@Observed+@ObjectLink: 将类标记为@Observed,并在子组件中通过@ObjectLink接收。这样框架会重写该类的 Setter,实现属性级监听。 -
重新赋值(引用触发): 强制让对象“变身”。
TypeScript
// 这种写法能触发更新,因为 user 的引用变了 this.user = { ...this.user, name: 'Bob' };
2. 为什么数组 push 后不刷新?
数组的操作是状态管理的“重灾区”。
根本原因:非原地修改与监听范围
ArkUI 能够自动识别数组的某些变异方法(如 push, pop, splice, shift 等),但在以下情况下会失效:
- 直接修改索引: 执行
this.list[0] = newItem。这不会触发 UI 更新,因为数组的引用没变,且这种赋值操作没有被框架的拦截器捕获。 - 嵌套对象的数组: 如果你
push了一个对象,后续修改这个对象的某个属性(如this.list[0].status = true),UI 不会刷新。因为数组只管“成员的增减”,不管“成员内部的细节”。
解决方案:
- 针对索引修改: 使用
this.list.splice(0, 1, newItem)或this.list = [...this.list]。 - 针对成员属性: 必须配合
@Observed装饰数组内的元素类,并结合LazyForEach的keyGenerator确保 Key 随数据变化。
3. 其他常见的“隐性 Bug”
A. 构造函数中的异步赋值
在 aboutToAppear 或 onCreate 中发起异步请求,直接给 @State 变量赋值。
- Bug 表现: 数据回来了,但组件已经渲染完毕,且由于闭包或作用域问题,某些复杂的嵌套组件没有收到更新通知。
- 规避: 确保异步回调在主线程执行,且优先使用
AppStorage或LocalStorage这种有全局中心调度能力的状态仓库。
B. “影子状态” (Shadow State)
在父组件修改了传给子组件 @Prop 的值,子组件 UI 没变。
- 原因: 如果子组件在内部也修改过这个
@Prop,由于@Prop是单向拷贝,父组件的后续同步可能会因为框架内部的 Diff 策略被“静默掉”或者产生非预期的覆盖。 - 规避: 严格遵循“单向数据流”,子组件想改数据?发事件(Emitter)给父组件改。
C. 循环依赖引发的死循环
在 build() 过程中修改了某个 @State(例如在计算宽度时又 set 了宽度)。
- Bug 表现: 应用卡死、帧率骤降、Log 报错
State changed during rendering。 - 规避:
build()函数必须是纯函数。严禁在渲染流程中写任何 Setter 逻辑。
总结:排查清单
当 UI 不刷新时,按以下顺序自查:
- 层级对吗? 超过一层的属性修改,用了
@Observed吗? - 引用变了吗? 数组或对象的操作是原地修改还是替换了新实例?
- Key 稳吗?
ForEach的 Key 是否唯一且能反映数据变化? - 改对地方了吗? 是不是在非 UI 线程(如 Worker)里偷偷改了数据?