前言
前面 18 篇(31–48)我们把 V1 状态管理体系用了个遍——@State、@Prop、@Link、@Provide/@Consume、@Observed+@ObjectLink、@Watch、@Reusable、LazyForEach。V1 成熟稳定,但有些本质局限:浅观察要整体赋值、@Observed 类必须 new 实例化、跨组件数据流装饰器多易混、批量更新要靠同调用栈。HarmonyOS 推出了V2 状态管理,用更统一的装饰器和深观察机制解决了这些痛点。
本篇是 V1 收尾总结,对比 V1 与 V2 的核心差异,为下篇 V2 迁移做铺垫。把V1 八装饰器复盘、V1 三大局限、V2 六装饰器预览、迁移决策四大要点讲透。
提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–48 篇。本篇是阶段二第十九篇,V1 收尾。
一、V1 八装饰器复盘
1.1 V1 状态管理全家福
| 装饰器 | 作用 | 核心篇 |
|---|---|---|
@State | 响应式状态,改变触发重渲染 | 第 31 |
@Prop | 父子单向只读,浅拷贝 | 第 40 |
@Link | 父子双向,$val 传引用 | 第 41 |
@Provide/@Consume | 跨层隐式共享,键名匹配 | 第 42 |
@Observed+@ObjectLink | 类实例深观察,改内部属性触发 | 第 43 |
@Watch | 状态变化副作用回调 | 第 39 |
@Reusable | ListItem 复用池,离屏入池 | 第 47 |
1.2 装饰器关系图
@State(响应式根基) ├─ @Prop(父子单向只读) ├─ @Link(父子双向) ├─ @Provide/@Consume(跨层隐式) ├─ @Observed+@ObjectLink(类实例深观察) └─ @Watch(变化副作用) @Reusable(列表复用,独立)关键经验:V1 以 @State 为根基,其他装饰器要么配合 @State(@Prop/@Link/@Watch/@Observed),要么独立处理列表复用(@Reusable)。
二、V1 三大局限
2.1 局限一:浅观察要整体赋值
@State combo: ComboInfo = { count: 0, multiplier: 1, lastMergeTime: 0 }; @State cats: Cat[] = []; // ❌ 改内部属性不触发 this.combo.count = 5; this.cats[0].y = 7; // ✅ 要整体赋值 this.combo = { ...this.combo, count: 5 }; this.cats = [...this.cats];痛点:嵌套对象/数组改内部属性要整体赋值,代码冗长且易忘——忘整体赋值就 UI 不刷新,是 V1 最经典 bug。
升级 @Observed 的代价:Cat 要从 interface 改成@Observed class,必须new实例化,子组件必须@ObjectLink——改动面大。
2.2 局限二:@Observed 类必须 new 实例化
@Observed export class Cat { constructor(id: string, level: CatLevel, x: number, y: number, falling: boolean) { /* ... */ } } // ✅ 必须 new const cat = new Cat('cat_0', CatLevel.SMALL, 0, 0, true); // ❌ 对象字面量不走构造器,不被 @Observed 标记 const cat = { id: 'cat_0', level: CatLevel.SMALL, x: 0, y: 0, falling: true };痛点:从 interface 改成 class 要写 constructor,所有实例化点从对象字面量改成 new——迁移成本高。
2.3 局限三:跨组件数据流装饰器多易混
| 场景 | 装饰器 | 传递语法 |
|---|---|---|
| 显示型只读 | @Prop | this.score |
| 编辑型双向 | @Link | this.$gameState |
| 跨层隐式 | @Provide/@Consume | 键名匹配 |
| 类深观察 | @ObjectLink | 普通值传 |
| 变化副作用 | @Watch | 字符串方法名 |
痛点:五种装饰器语义接近,初学者易混——@Prop 还是 @Link?@ObjectLink 还是 @Prop?@Watch 字符串拼错不报错。
三、V2 六装饰器预览
3.1 V2 状态管理全家福
| 装饰器 | 作用 | V1 对应 |
|---|---|---|
@Local | 本地响应式状态 | @State |
@Param | 父子只读接收 | @Prop |
@Event | 子触发父回调 | 新(V1 要手写函数 prop) |
@Once | 单次初始化 | 新 |
@ObservedV2+@Trace | 类实例深观察(字段级) | @Observed+@ObjectLink |
@Monitor | 状态变化监听(多字段) | @Watch |
3.2 V2 的统一简化
// V2:@Local 替代 @State @Local score: number = 0; // V2:@Param 替代 @Prop(且深观察自动) @Param combo: ComboInfo; // 改 combo.count 也触发! // V2:@Event 替代手写函数 prop @Event onScoreAdd: (delta: number) => void; // V2:@ObservedV2 + @Trace 替代 @Observed + @ObjectLink @ObservedV2 export class Cat { @Trace id: string = ''; @Trace x: number = 0; @Trace y: number = 0; // 字段级深观察,改 cat.y 触发 } // V2:@Monitor 替代 @Watch(可监多字段) @Monitor('combo.count, combo.multiplier') onComboChange(monitor: Monitor): void { /* ... */ }关键经验:V2 用更少装饰器覆盖更多场景——@Param 自动深观察省了 @ObjectLink,@Event 替代手写函数 prop 简化跨组件触发。
3.3 V2 深观察的字段级革命
// V1 @Observed:整类深观察 @Observed export class Cat { id: string; // 所有字段都深观察 y: number; } // V2 @ObservedV2+@Trace:字段级深观察 @ObservedV2 export class Cat { id: string = ''; // 不打 @Trace,不深观察 @Trace y: number = 0; // 只 y 深观察 }关键经验:V2 @Trace 字段级深观察——只监听需要响应的字段,省无谓依赖追踪开销。
四、V1 vs V2 核心差异表
4.1 装饰器对照
| 场景 | V1 | V2 | V2 优势 |
|---|---|---|---|
| 本地状态 | @State | @Local | 语义更清晰(本地) |
| 父子只读 | @Prop(浅拷贝) | @Param(深观察) | 改内部属性也触发 |
| 父子双向 | @Link+$val | @Param+@Event | 分离数据与事件 |
| 跨层共享 | @Provide/@Consume | @Provide/@Consume | 保留(V2 也支持) |
| 类深观察 | @Observed+@ObjectLink | @ObservedV2+@Trace | 字段级深观察 |
| 变化监听 | @Watch(单字段) | @Monitor(多字段) | 一次监多字段 |
| 列表复用 | @Reusable | @Reusable | 保留 |
4.2 浅观察 vs 深观察
| 维度 | V1 @State/@Prop | V2 @Local/@Param |
|---|---|---|
| 嵌套对象改属性 | ❌ 要整体赋值 | ✅ 直接触发 |
| 数组项改属性 | ❌ 要两层赋值 | ✅ 直接触发 |
| 实例化 | interface 字面量 OK | class new(V2 也) |
| 代码量 | 多(整体赋值) | 少(改属性即可) |
4.3 批量更新
| 维度 | V1 | V2 |
|---|---|---|
| 被动批量 | ✅ 同调用栈 | ✅ 同调用栈 |
| 主动批量 | ❌ 无(要 setTimeout 套路) | ✅batchUpdateAPI |
4.4 类深观察对比
// V1:整类深观察 + 子必须 @ObjectLink @Observed export class Cat { id: string; y: number; /* 所有字段 */ } @Component struct CatItem { @ObjectLink cat: Cat; // 必须用 @ObjectLink build() { /* ... */ } } // V2:字段级深观察 + 子用 @Param(自深观察) @ObservedV2 export class Cat { id: string = ''; // 不 @Trace,不深观察 @Trace y: number = 0; // 只 y 深观察 } @Component struct CatItem { @Param cat: Cat; // 用 @Param,自深观察 build() { /* ... */ } }关键经验:V2 用 @Param 替代 @ObjectLink——@Param 自带深观察(接 @ObservedV2 实例),不用单独装饰器。
五、迁移决策
5.1 何时迁 V2
| 场景 | 推荐 |
|---|---|
| 新项目 | ✅ 直接上 V2 |
| 老项目小改 | ⚠️ 混用 V1 V2(同组件不可混) |
| 老项目大改(状态管理痛点明显) | ✅ 全迁 V2 |
| 老项目稳定无痛点 | ❌ 保持 V1 |
5.2 混用限制
// ⚠️ 同组件内 V1 V2 不能混 @Component struct BadMixed { @State score: number = 0; // V1 @Local combo: ComboInfo = { /* ... */ }; // V2 // 编译报错或行为未定义 }关键经验:V1 V2 不能在同一组件混用——整个组件要么全 V1 要么全 V2,但不同组件可以分别用 V1 V2。
5.3 迁移工作量评估
| 迁移项 | 工作量 | 收益 |
|---|---|---|
| @State → @Local | 低(改名) | 语义清晰 |
| @Prop → @Param | 低(改名) | 深观察,省整体赋值 |
| @Link → @Param+@Event | 中(拆双向) | 数据/事件分离 |
| @Observed → @ObservedV2+@Trace | 中(字段标 @Trace) | 字段级深观察 |
| @ObjectLink → @Param | 低(改名) | 少一装饰器 |
| @Watch → @Monitor | 中(改签名) | 多字段监听 |
关键经验:迁移核心收益是「深观察省整体赋值」和「字段级精细监听」——痛点明显的项目迁 V2 值得。
六、猫猫大作战 V1 现状总结
6.1 当前 V1 用到的装饰器
| 装饰器 | 用处 | 篇 |
|---|---|---|
| @State | score、cats、combo、gameState 等所有响应式状态 | 31–45 |
| @Watch | gameState 变化触发音效/震动 | 39 |
| @Provide/@Consume | gameState、score 跨层共享给嵌套子组件 | 42 |
| @Observed+@ObjectLink | Cat 类深观察,CatItem 子组件 | 43、44 |
| @Reusable | RecordItem 列表复用(未来战绩页) | 47 |
6.2 当前 V1 痛点
| 痛点 | 当前解法 | V2 解法 |
|---|---|---|
| combo 嵌套对象改属性要整体赋值 | {...this.combo, count: 5} | this.combo.count = 5直接触发 |
| cats 数组项改属性要两层赋值 | 引擎返回新数组整体赋值 | this.cats[0].y = 7直接触发 |
| gameState 变化监听单字段 | @Watch(‘onGameStateChange’) | @Monitor 可监多字段 |
| PauseOverlay 改父 gameState | @Link + $val | @Param + @Event 分离 |
关键经验:猫猫大作战当前痛点集中在「嵌套对象/数组整体赋值」——迁 V2 能显著简化。
6.3 迁移优先级建议
优先迁: 1. @State → @Local(改名简单,收益语义清晰) 2. @Prop → @Param(改 combo/cats 等嵌套对象为深观察) 3. @Observed → @ObservedV2+@Trace(Cat 字段级深观察) 可选迁: 4. @Link → @Param+@Event(PauseOverlay 等编辑型组件) 5. @Watch → @Monitor(gameState 监听) 保留: 6. @Provide/@Consume(V2 也支持,不改) 7. @Reusable(V2 也支持,不改)七、踩坑提示
7.1 V1 V2 同组件混用
// ❌ 错误:同组件混用,编译报错 @Component struct Bad { @State score: number = 0; // V1 @Local combo: ComboInfo = { /* ... */ }; // V2 } // ✅ 正确:整个组件统一 V1 或 V2 @Component struct GoodV1 { @State score: number = 0; @State combo: ComboInfo = { /* ... */ }; } @Component struct GoodV2 { @Local score: number = 0; @Local combo: ComboInfo = { /* ... */ }; } `` ### 7.2 以为 V2 全替代 V1 ```ts // ⚠️ V2 不是全替代,有些 V1 装饰器 V2 也支持 @Provide/@Consume // V1 V2 都有,不变 @Reusable // V1 V2 都有,不变实战经验:V2 是 V1 的进化,不是替代——@Provide/@Consume/@Reusable 保持,@State/@Prop/@Link/@Observed/@Watch 升级。
7.3 迁移忘改实例化
// V1 @Observed class @Observed export class Cat { /* ... */ } const cat = new Cat(/* ... */); // V2 @ObservedV2 class @ObservedV2 export class Cat { @Trace y: number = 0; } // 还是 new 实例化,不变 const cat = new Cat(/* ... */); // ⚠️ 但如果 V1 用 interface 对象字面量,迁 V2 要改成 class new `` ## 八、调试技巧 1. **DevEco 版本支持**:确认 DevEco Studio 和 SDK 支持 V2(API 12+)。 2. **`console.info` 对比 V1 V2 行为**:迁后改属性看是否触发(V2 应触发)。 3. **混用排查**:编译报「V1 V2 不能混用」,检查组件内装饰器。 4. **@Trace 忘标**:迁 @ObservedV2 后忘给字段标 @Trace,该字段不深观察。 ## 九、性能与最佳实践 1. **新项目直接上 V2**——深观察省整体赋值,字段级精细监听。 2. **老项目痛点明显才迁**——稳定无痛点保持 V1,迁要全组件迁。 3. **V1 V2 不能同组件混用**——整个组件统一选一版。 4. **@Provide/@Consume/@Reusable V1 V2 都支持**——迁移时这两个保留。 5. **迁移核心收益是深观察**——嵌套对象/数组改属性直接触发,省整体赋值。 6. **@Monitor 多字段监听**——一次监听多个字段变化,比 @Watch 灵活。 ## 十、阶段二进度小结(31–49) 本篇是阶段二「状态管理 + 交互 + 动画」第 19 篇,V1 收尾: | 篇 | 主题 | 核心要点 | |----|------|----------| | 31 | @State | 响应式状态 | | 32 | 多 @State | 同帧批量 | | 33 | setInterval 主循环 | 100ms 物理周期 | | 34 | 计时器 | 1000ms 秒级 | | 35 | spawnTimer | 2000ms 自动生成 | | 36 | clearTimers | -1 哨兵,三时机清理 | | 37 | onClick 列投放 | 闭包捕获 col | | 38 | 箭头函数 this | 回调统一箭头 | | 39 | @Watch | 状态变化副作用 | | 40 | @Prop | 父子单向只读 | | 41 | @Link | 父子双向 $val | | 42 | @Provide/@Consume | 跨层隐式共享 | | 43 | @Observed+@ObjectLink | 类实例深观察 | | 44 | 数组项替换 | ForEach 密钥 diff | | 45 | 批量更新 | 被动批量 + batchUpdate | | 46 | 嵌套陷阱 | 浅观察整体赋值 | | 47 | @Reusable | 列表复用池 | | 48 | LazyForEach | 大列表按需渲染 | | 49(本篇) | V1 局限总结 | V1 V2 差异 | 接下来第 50 篇会专讲 V2 迁移——@Local/@Param/@Event/@Trace/@Monitor 的实战。 ## 总结 本篇我们做 V1 收尾总结,掌握了**V1 八装饰器复盘**、**V1 三大局限(浅观察、@Observed 必须 new、装饰器多易混)**、**V2 六装饰器预览**、**迁移决策(新项目直接 V2,老项目痛点明显才迁)**四大要点。核心要点:**V1 浅观察要整体赋值是最大痛点;V2 @Param 自深观察省整体赋值;@Trace 字段级精细监听;V1 V2 不能同组件混用**。 下一篇我们将专讲 V2 迁移实战——@Local/@Param/@Event/@ObservedV2+@Trace/@Monitor 的改造。 > 如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力! --- **相关资源:** - 「猫猫大作战」项目源码:本仓库 `entry/src/main/ets/pages/Index.ets`、`entry/src/main/ets/components/` - [ArkUI V2 状态管理官方指南](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-v2) - [ArkUI V1 V2 差异对比官方文档](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-state-management-overview) - [ArkUI 状态管理迁移最佳实践](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/bpta-state-migration) - [开源鸿蒙跨平台社区](https://openharmonycrossplatform.csdn.net) - [HarmonyOS 开发者官方文档首页](https://developer.huawei.com/consumer/cn/doc/) - 系列索引:本仓库 `articles/INDEX.md`