HarmonyOS应用开发实战:猫猫大作战-V1 八装饰器复盘、V1 三大局限、V2 六装饰器预览、迁移决策


前言
前面 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 套路) | ✅ batchUpdate API |
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`
更多推荐

所有评论(0)