为什么需要更强的状态管理
界面简单时,一个 @State 就够了。可一旦状态要跨组件传递、要放进独立类里管理,旧的装饰器组合就开始绕——谁该持有、谁该同步、谁改了谁刷新,全靠人肉记。状态管理 V2 的目标就是把这件事讲清楚:数据从哪来、谁能改、改了谁刷新。
状态管理的核心问题只有一个:状态变化如何自动、准确地传导到依赖它的界面。
这篇顺着装饰器到数据代理,把鸿蒙的响应式模型讲透。
从 @State 到状态管理 V2
@State 管视图内部的简单值。当状态是一个对象、要在父子组件间流动,就需要把整个对象做成可观测。V2 提供更简洁的数据代理写法,让普通类自动获得响应式能力,不用一层层套旧装饰器。
@ObservedV2
class Cart {
@Trace var count: number = 0
@Trace var total: number = 0
}
标注之后,count、total 变成可被界面追踪的属性。
@Observed 与 @ObjectLink
旧版里,一个类用 @Observed 标注,子组件用 @ObjectLink 接住它的实例。这样父组件持有对象,子组件引用同一个对象,任一属性变化都能触发两端刷新。它是跨组件共享对象的经典组合。
- @State视图内部私有状态。
- @Prop父到子单向复制。
- @Link父子双向同步。
- @Observed/@ObjectLink共享对象引用。
@Prop 与 @Link 的边界
@Prop 是值的拷贝:子组件改自己的副本,不影响父级。@Link 是双向绑定:子组件改,父级跟着变。选错就会出现"改了界面不更新"或"误改了父状态"的诡异 bug。传基本类型、子只读用 @Prop;要回写用 @Link。
跨层级共享:@Provide/@Consume
状态要跨很深的层级传,一层层 props 太啰嗦。@Provide 在祖先组件提供值,@Consume 在后代任意层级直接取用,省去中间组件透传。适合主题、用户信息这类全局共享。
代价是数据流变得隐式——看单个组件看不出值从哪来,调试时要往上翻提供方。全局东西才用它,局部状态还是显式传。
V2 的 @Trace 与属性监听
V2 用 @Trace 标注要追踪的属性,比旧版的对象级追踪更精细——只有被追踪的属性变化才触发刷新,性能更好、依赖关系更清楚。配合 computed 式的派生值,可以让界面只读计算结果,不直接依赖中间状态。
@Trace var count = 0
get double(): number { return this.count * 2 }
count 变,double 自动跟着算,界面只关心 double。
避坑:什么时候触发刷新
最常见的坑:改了数组元素,但界面不更新。原因是只改了对象内部、或没追踪到这个属性。把可变状态收敛到可观测类里、确保被追踪属性真的被界面读取,刷新才会发生。
- 别在普通变量上指望响应式。
- 界面没读到的属性,改了也不会重绘。
- 嵌套对象要整体标注可观测。
实战:一个计数器状态模型
把计数与小计放进一个可观测类,父子组件共享它。
@ObservedV2
class Counter {
@Trace var n = 0
get doubled(): number { return this.n * 2 }
inc() { this.n += 1 }
}
@Component
struct Child {
@Local c: Counter = new Counter()
build() { Text(`${this.c.n} / ${this.c.doubled}`) }
}
点一次 inc,n 和派生的 doubled 一起变,界面自动刷新。状态管理做到这份上,界面就真的成了状态的函数——你只管改状态,剩下的交给框架。