改了嵌套对象里的一个字段,整页都在闪——V2 的 @Trace 救的就是这个
一个商品详情页,状态里挂着一个大对象:商品信息、库存、价格、规格、用户优惠券。用户点一下“选中红色款”,只改了 selectedSku.color 这一个字段,整个页面的标题、价格、评价、推荐列表全部重新构建了一遍。
V1 的状态观察是对象级的:@State 挂了一个对象,对象内部的任何字段变化,依赖这个对象的组件全部刷新。V2 的核心改进是把观察粒度从“对象”降到“属性”:@ObservedV2 标记类,@Trace 标记需要观测的字段,只有真正依赖那个变化字段的组件才会进入刷新队列。

不给 @Trace 的字段,改了也不刷新
@ObservedV2 本身不追踪任何属性,它只是声明“这个类可以被观察”。真正让某个字段具备观测能力的是 @Trace。
typescript
@ObservedV2
class ProductState {
@Trace selectedColor: string = 'red'
@Trace selectedSize: string = 'M'
stock: number = 0
price: number = 0
}
selectedColor 和 selectedSize 加了 @Trace,修改它们会触发依赖组件刷新。stock 和 price 没有加 @Trace,直接改 this.state.stock = 10 不会引起任何 UI 更新。如果 stock 也需要驱动 UI,就必须补上 @Trace。
常见错误是只给对象加了 @ObservedV2,忘了给字段加 @Trace,然后发现数据改了页面纹丝不动,回头怀疑是不是 V2 没生效。V2 的观测是声明式的:你标记了什么,它观测什么;没标记的,它完全不碰。
@Computed 缓存派生状态,避免重复计算
详情页里有一个“到手价”需要根据基础价格、优惠券、会员折扣一起算出来。如果用 get 方法,每次 UI 刷新都会重新执行一遍计算,即使依赖的三个值都没变。
@Computed 把计算结果缓存起来,只有依赖的 @Trace 字段变化时才重新计算。
typescript
@ObservedV2
class PriceState {
@Trace basePrice: number = 100
@Trace couponAmount: number = 10
@Trace isMember: boolean = false
@Computed
get finalPrice(): number {
let price = this.basePrice - this.couponAmount
if (this.isMember) price *= 0.95
return Math.max(price, 0)
}
}
finalPrice 在 basePrice、couponAmount、isMember 任何一个变化之前,读到的都是缓存值。依赖不变时,@Computed 不会重复执行计算逻辑。对于多字段聚合计算的场景,它比在 build() 里每次重新算要省得多。

数组和嵌套对象要逐层标记
V2 的观测不会自动向下递归。一个 @Trace 数组,元素如果是普通对象,修改元素内部字段不会刷新;元素本身也得是 @ObservedV2 类,并且内部字段标了 @Trace,链路才完整。
typescript
@ObservedV2
class SkuItem {
@Trace color: string = ''
@Trace stock: number = 0
}
@ObservedV2
class DetailState {
@Trace skus: SkuItem[] = []
}
this.state.skus[0].stock = 5 触发刷新的前提是 SkuItem 的 stock 标了 @Trace。如果 SkuItem 只是普通类,或者 stock 没标 @Trace,修改不会向上传播到依赖 skus 的组件。
数组元素的增删改用整体赋值或者 V2 提供的数组方法,确保变化能被 @Trace 捕获。直接 push 有时能触发,有时不能,取决于数组是否被 @Trace 修饰以及框架的版本行为,整体赋值是最不依赖实现细节的写法。
从 V1 迁 V2 先拆状态粒度,再换装饰器
V2 迁移最常见的返工是“装饰器换了,粒度没换”:把一个巨大的 @State 对象原封不动改成 @Local,内部字段照旧没有 @Trace,页面刷新行为可能比 V1 还差。
迁移的正确顺序是先拆状态归属,再决定哪些字段需要 @Trace。一个字段只要不驱动任何 UI,就不该标 @Trace;一个状态只被一两个叶子组件使用,就不该挂在页面根组件上。@Trace 的收益来自“精确标脏”,标记范围收得越准,刷新范围越小。
V2 不是“V1 换了个名字”,它把观测的声明权交给了开发者。标对了,只刷新该刷的;标漏了,数据变了页面不动;标多了,刷新范围又回去了。先画清楚哪些字段驱动哪些组件,再动手加 @Trace,比迁移完再逐个页面排查“为什么不刷新”要快得多。
更多推荐
所有评论(0)