ArkTS 装饰器 V2(官方称为状态管理 V2)是 HarmonyOS NEXT(API 11+)推出的新一代响应式系统。它彻底重构了状态观察机制,解决了 V1 版本"深层观测失效"和"对象引用断开"等痛点。


一、为什么需要 V2?V1 的痛点

在 V1 体系中(@State/@Prop/@Link),存在几个噩梦场景:

// V1 痛点 1:深层属性变化检测不到
@Observed
class Person {
  profile: { name: string, age: number } = { name: "张三", age: 20 };
}
// 修改 person.profile.age 不会触发 UI 刷新,因为 @Observed 只观察对象本身,不观察嵌套属性

// V1 痛点 2:数组/对象赋值后观察链断裂
@State list: string[] = ["a", "b"];
// 执行 this.list = ["c", "d"] 后,之前的观察器失效

V2 的核心改进

  • 细粒度追踪:精确到属性级别的变更检测
  • 引用稳定性:对象赋值后观察关系依然保持
  • 类型严格:充分利用 ArkTS 静态类型系统
  • 性能提升:减少不必要的组件重绘

二、V2 装饰器全景图

V2 引入了全新的装饰器家族,与 V1 完全不兼容(一个组件要么全用 V1,要么全用 V2):

V2 装饰器 对应 V1 作用 关键差异
@ComponentV2 @Component 标记组件 启用 V2 运行时
@Local @State 组件内部状态 支持深层观察
@Param @Prop 父传子(只读) 支持深层观察,不可修改
@Event 无直接对应 子传父回调 类型安全的事件发射
@Once - 一次性同步 只同步一次,后续父变化不更新子
@Provider @Provide 祖先提供依赖 支持接口类型,作用域更明确
@Consumer @Consume 后代消费依赖 强制类型匹配
@ObservedV2 @Observed 类可观察标记 必须配合 @Trace 使用
@Trace - 属性级观察标记 精确追踪字段变化

三、核心机制详解

1. @ObservedV2 + @Trace:属性级精准观察

V2 不再自动观察对象的所有属性,而是采用显式标记策略:

@ObservedV2
class Todo {
  @Trace content: string = "";      // 被追踪:修改会触发 UI 刷新
  @Trace isDone: boolean = false;   // 被追踪
  
  createdAt: Date = new Date();     // 未被追踪:修改不会触发 UI
  id: number = 0;                   // 未被追踪
}

// 使用
@Entry
@ComponentV2
struct TodoPage {
  @Local todo: Todo = new Todo();
  
  build() {
    Column() {
      Text(this.todo.content) // 自动订阅 content 的变化
      
      Button('修改内容')
        .onClick(() => {
          this.todo.content = "新内容"; // ✅ 触发 UI 刷新
          this.todo.id = 999;           // ❌ 不触发刷新(性能优化)
        })
    }
  }
}

深层观察@Trace 会自动递归观察嵌套对象(只要嵌套对象也是 @ObservedV2):

@ObservedV2
class Profile {
  @Trace address: Address = new Address(); // 嵌套对象
}

@ObservedV2  
class Address {
  @Trace city: string = "";
}

// 修改 this.user.profile.address.city 会正确触发 UI 刷新

2. @Local:更强大的组件状态

替代 @State,但支持对象赋值保持观察

@ComponentV2
struct Counter {
  @Local count: number = 0;
  @Local user: User = new User();
  
  build() {
    Column() {
      Text(`${this.count}`)
      Button('重置对象')
        .onClick(() => {
          // V1 中这会丢失观察;V2 中继续正常工作
          this.user = new User(); 
        })
    }
  }
}

3. @Param 与 @Event:重新设计的父子通信

V1 中 @Prop 是单向同步,@Link 是双向同步,容易混淆。V2 采用了更明确的命令式:

// 子组件定义
@ComponentV2
struct Child {
  @Param message: string;      // 只读输入,类似 props
  @Param count: number;
  @Event onCountChange: (val: number) => void; // 明确的事件回调
  
  build() {
    Column() {
      Text(this.message) // 只能读取,不能修改
      
      Button(`当前: ${this.count}`)
        .onClick(() => {
          // 通过事件通知父组件,而不是直接修改
          this.onCountChange(this.count + 1);
        })
    }
  }
}

// 父组件使用
@Entry
@ComponentV2
struct Parent {
  @Local parentCount: number = 10;
  
  build() {
    Child({
      message: "来自父组件",
      count: this.parentCount,
      onCountChange: (val) => { this.parentCount = val } // 类型安全的回调
    })
  }
}

@Once 修饰符:用于只需要初始同步一次的场景(如配置项):

@ComponentV2
struct ConfigurableItem {
  @Param @Once config: AppConfig; // 只同步一次,后续父修改不影响子
  
  build() {
    Text(this.config.name) // 初始有值,之后不随父更新
  }
}

4. @Provider / @Consumer:依赖注入 2.0

V2 的依赖注入支持接口类型多实例注入

// 定义接口
interface ITheme {
  primaryColor: string;
  fontSize: number;
}

@ObservedV2
class DarkTheme implements ITheme {
  @Trace primaryColor: string = "#000000";
  @Trace fontSize: number = 16;
}

@Entry
@ComponentV2
struct App {
  @Provider('theme') theme: ITheme = new DarkTheme(); // 提供依赖,用 key 标识
  
  build() {
    Column() {
      DeepChild()
    }
  }
}

// 深层子组件
@ComponentV2
struct DeepChild {
  @Consumer('theme') theme: ITheme; // 通过 key 消费
  
  build() {
    Text("深色模式文本")
      .fontColor(this.theme.primaryColor) // 自动响应主题变化
  }
}

四、V1 vs V2 行为对比

场景 1:数组元素交换

// V1:交换两个元素位置,UI 不更新(因为数组引用没变,内容变了但 V1 检测不到)
@State list: string[] = ["A", "B", "C"];

// V2:精确追踪数组内容变化,交换后立即刷新
@Local list: string[] = ["A", "B", "C"];

场景 2:嵌套对象修改

@ObservedV2
class Outer {
  @Trace inner: Inner = new Inner();
}

@ObservedV2  
class Inner {
  @Trace value: number = 0;
}

// V1:修改 outer.inner.value 需要 @ObjectLink 层层传递,极其繁琐
// V2:直接修改,自动触发所有相关 UI 刷新

五、迁移指南:何时使用 V2?

推荐使用 V2 的场景:

  • 需要深层观察(对象套对象)
  • 频繁进行数组/对象整体赋值this.list = newList
  • 需要类型安全的跨组件通信
  • 新项目(API 11+)

继续使用 V1 的场景:

  • 维护旧项目(API 9/10)
  • 简单状态管理(单层对象,无嵌套)
  • 需要与现有 V1 组件库混用(V1 和 V2 组件不能互相嵌套)

混用限制(重要!)

@Component   // V1 组件
struct OldComp { ... }

@ComponentV2 // V2 组件  
struct NewComp { ... }

// ❌ 错误:V1 中不能包含 V2,V2 中也不能包含 V1
OldComp() {
  NewComp() // 编译错误!
}

六、完整 V2 实战示例

@ObservedV2
class Task {
  @Trace id: number;
  @Trace title: string;
  @Trace completed: boolean;
  
  constructor(id: number, title: string) {
    this.id = id;
    this.title = title;
    this.completed = false;
  }
}

@Entry
@ComponentV2
struct TaskManager {
  @Local tasks: Task[] = [];
  @Local newTaskTitle: string = '';
  
  private nextId: number = 1;
  
  addTask() {
    if (!this.newTaskTitle.trim()) return;
    const task = new Task(this.nextId++, this.newTaskTitle);
    this.tasks.push(task); // V2 正确追踪数组变化
    this.newTaskTitle = '';
  }
  
  toggleTask(task: Task) {
    task.completed = !task.completed; // 精确追踪 completed 属性
  }
  
  build() {
    Column({ space: 16 }) {
      // 输入区
      Row() {
        TextInput({ text: $$this.newTaskTitle })
          .width('80%')
        Button('添加')
          .onClick(() => this.addTask())
      }
      
      // 统计(V2 自动更新)
      Text(`待完成: ${this.tasks.filter(t => !t.completed).length}`)
        .fontColor('#666')
      
      // 列表
      List() {
        ForEach(this.tasks, (task: Task) => {
          TaskItem({
            task: task,
            onToggle: () => this.toggleTask(task)
          })
        }, (task: Task) => task.id.toString())
      }
    }
    .padding(20)
  }
}

@ComponentV2
struct TaskItem {
  @Param task: Task;
  @Event onToggle: () => void;
  
  build() {
    ListItem() {
      Row() {
        Text(this.task.title)
          .decoration({ 
            type: this.task.completed ? TextDecorationType.LineThrough : TextDecorationType.None 
          })
          .opacity(this.task.completed ? 0.5 : 1.0)
        
        Toggle({ type: ToggleType.Checkbox, isOn: this.task.completed })
          .onChange(() => this.onToggle())
      }
    }
  }
}

七、总结

装饰器 V2 是 ArkTS 走向工程级应用的关键升级:

  1. 精确观察@Trace 让性能优化具体到字段级,避免无效刷新
  2. 引用安全:赋值操作不再破坏响应式链条
  3. 类型严格@Param/@Event 提供编译期类型检查,减少运行时错误
  4. 设计模式友好:更清晰的单向数据流,拥抱函数式更新理念

建议:如果你刚开始学习 ArkUI,直接从 V2 入手。它虽然多了 @ObservedV2@Trace 的样板代码,但省去了后期调试观察失效的痛苦。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐