如果你已经习惯了 状态管理 V1 (@Component) 的开发模式,切换到 V2 (@ComponentV2) 可能会让你感到既熟悉又陌生。V2 不仅仅是简单的语法糖升级,它在状态管理渲染性能上有着本质的改变。

这篇文章将带你快速掌握核心区别,助你无缝迁移。


1. 核心思维转变:从“代理”到“深度观察”

  • V1 的痛点:V1 的状态观察通常是“浅层”的。修改嵌套对象的属性(如 this.obj.a.b = 1)往往无法触发 UI 刷新,除非你手动用 ObjectLink 或重新赋值整个对象。
  • V2 的进化:V2 引入了深度观察能力。通过新的装饰器,框架能更精准地感知数据变化,从而实现更细粒度的 UI 更新,大幅减少不必要的重绘。

2. 装饰器对照表 (Cheatsheet)

这是最直接的变化,请牢记这张映射表:

功能

V1 (@Component)

V2 (@ComponentV2)

核心区别

组件声明

@Component

@ComponentV2

V2 组件不再支持继承其他组件

内部状态

@State

@Local

@Local 具备深度观察能力

外部参数

@Prop (单向) / @Link (双向)

@Param

V2 默认单向传递。不再区分 Prop/Link。父组件传值,子组件只读。

双向绑定

@Link

@Param + @Event

V2 推荐通过事件回调修改父组件数据,或使用双向绑定语法糖 !!

跨层级数据

@Provide / @Consume

@Provider / @Consumer

别名更统一,能力更强

普通成员

(无装饰器)

(无装饰器)

V2 中普通变量变化绝对不会触发 UI 刷新

计算属性

(无直接对应)

@Computed

新增!类似于 Vue 的 computed,带缓存,依赖变化自动更新

监听变化

@Watch

@Monitor

@Monitor 更强大,支持监听多个属性,且能获取变化前后的值


3. 生命周期:新增的关键一环

V1 中我们常苦恼于 aboutToAppear 时节点还没创建,无法操作底层。V2 完美解决了这个问题:

  • aboutToAppear:组件创建后,build 执行前。适合初始化数据。
  • onDidBuild (新增)build 执行完毕,UI 节点创建完成。适合获取 FrameNode、绑定手势、计算尺寸。
  • aboutToDisappear:组件销毁前。

迁移建议:如果你在 V1 中使用 setTimeout(0) 来 hack 获取节点,请立即改用 V2 的 onDidBuild


4. 实战代码对比

场景:父子组件传值与更新

V1 写法:

@Component
struct Child {
  @Link count: number; // 双向绑定

  build() {
    Button(`Count: ${this.count}`)
      .onClick(() => {
        this.count++; // 直接修改,父组件同步更新
      })
  }
}

V2 写法:

@ComponentV2
struct Child {
  @Param count: number = 0; // 只读!直接修改会报错
  @Event changeCount: (val: number) => void; // 声明事件

  build() {
    Button(`Count: ${this.count}`)
      .onClick(() => {
        // this.count++; // 错误:@Param 是只读的
        this.changeCount(this.count + 1); // 正确:通知父组件更新
      })
  }
}

// 父组件调用
// Child({ count: this.myCount, changeCount: (v) => this.myCount = v })
// 或者使用语法糖:
// Child({ count: this.myCount!! }) 

5. 核心痛点解决:实体对象变化的深度观察

这可能是 V1 和 V2 最大的体验分水岭。V1 是“整体替换”,V2 是“精准手术”。

V1 的痛点:必须“整体替换”或“强行包装”

在 V1 中,@State 装饰的对象,默认只能观察到第一层的变化。

假设你有一个实体:

class User {
  name: string = "Jack"
  age: number = 18
}

@Component
struct UserCard {
  @State user: User = new User()

  build() {
    Column() {
      Text(this.user.name)
      Button("变老").onClick(() => {
        // 错误写法:直接修改属性,UI 不会刷新!
        this.user.age++ 
        
        //  V1 正确写法 1:重新赋值整个对象(性能差,因为要创建新对象)
        // this.user = new User(this.user.name, this.user.age + 1)
        
        //  V1 正确写法 2:使用 @Observed + @ObjectLink(繁琐,需要拆分组件)
        // 必须把 User 类标记为 @Observed,且 UserCard 必须把 user 传给子组件,
        // 子组件用 @ObjectLink 接收,才能在子组件里修改属性触发刷新。
      })
    }
  }
}

V2 的进化:@ObservedV2 + @Trace 实现“深度监听”

V2 引入了真正的代理(Proxy)机制,它可以深入到对象的每一个属性。

同样的实体,在 V2 中这样写:

// 1. 给类打上 @ObservedV2 标签
@ObservedV2
class User {
  // 2. 给需要观察的属性打上 @Trace 标签
  @Trace name: string = "Jack"
  @Trace age: number = 18
}

@ComponentV2
struct UserCard {
  // 使用 @Local 接收
  @Local user: User = new User()

  build() {
    Column() {
      Text(this.user.name)
      Button("变老").onClick(() => {
        // V2 完美写法:直接修改属性,UI 自动刷新!
        this.user.age++ 
      })
    }
  }
}

核心对比表

操作场景

V1 (@State / @Observed)

V2 (@Local / @ObservedV2)

修改对象属性
user.age++

不刷新
(除非用 ObjectLink)

刷新
(只要有 @Trace)

替换整个对象
user = new User()

刷新

刷新

嵌套对象修改
user.addr.city = "X"

极难处理
(需多层 ObjectLink)

轻松支持
(只需嵌套类也是 ObservedV2)

数组元素修改
list[0].name = "X"

不刷新
(除非元素是 Observed)

刷新
(只要元素类有 Trace)


6. 常见坑与避雷指南

  1. 混用禁止:同一个 Struct 只能用 V1 或 V2,不能同时加两个装饰器。但在同一个项目中,V1 和 V2 组件可以互相引用(父 V1 含子 V2,或反之)。
  2. @Param 默认值:V2 中 @Param 必须要在本地给默认值,或者标记为可选,否则编译报错。
  3. 数组更新:V2 对数组的操作(push, splice 等)能被更好地捕获,但依然建议遵循不可变数据的最佳实践。
  4. 类对象:如果你自定义了 Class 并希望它能触发 UI 更新,记得给 Class 加上 @ObservedV2,给属性加上 @Trace。这是 V2 深度观察的基石。

7. 总结

V2 牺牲了一点点“随手修改”的便利性(比如 @Link 的直接赋值),换来了更清晰的数据流更强悍的性能

  • 新项目:强烈建议直接全量使用 V2。
  • 老项目:可以渐进式迁移。先从性能瓶颈大的复杂列表项组件开始重构为 V2。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐