鸿蒙开发笔记-8-组件状态管理装饰器:@Provide/@Consume与@Observed/@ObjectLink

32 阅读4分钟

一、基本概念

1. @Provide与@Consume:跨组件树双向同步

这对装饰器实现了任意层级组件间的双向数据绑定。@Provide在祖先组件中声明共享状态,@Consume在后代组件中消费该状态,形成类似"发布-订阅"的机制。其突破传统父子传参的限制,支持组件树中任意层级的通信。

  • 自动广播机制:@Provide变量变更时,所有关联@Consume组件自动更新
  • 别名匹配规则:支持通过相同变量名或别名建立绑定(如@Provide('count')与@Consume('count'))
  • 类型严格校验:要求@Provide与@Consume变量类型完全一致
  • 支持ObjectClassArray等复杂类型

2. @Observed与@ObjectLink:深度数据观察

这对装饰器专为解决嵌套对象/数组的深度监听问题而设计。传统@State只能感知对象引用的变化,而@Observed通过代理机制实现对类属性的细粒度观察,@ObjectLink则建立与父组件@Observed实例的双向绑定。

  • 支持多层嵌套属性变更(如user.address.city)
  • 支持MapSet等复杂数据结构(需API 11+)
  • 可观察数组元素的增删改操作, 数组操作需保持引用(如this.order.items = [...this.order.items]
  • 类对象属性的原子级更新触发UI刷新
  • 原子同步:@ObjectLink建立与父组件@Observed实例的直接引用关系,避免整体对象替换的性能损耗
  • 批量更新:使用requestAnimationFrame合并多次属性修改,减少渲染次数
  • 局部更新控制:通过@Track标记需要观察的属性
  • 惰性加载:对深层嵌套数据动态加载@Observed类,降低内存占用
  • 精准监听:通过@Track标记关键属性,避免全量更新(如仅监听user.name
  • 避免混用装饰器:@Observed类不可与其他类装饰器(如@Serializable)共用

二、使用方法

1. @Provide与@Consume

// 祖先组件提供共享主题配置
@Component
struct ThemeProvider {
  @Provide themeConfig = { 
    primaryColor: '#2196F3',
    fontSize: 16
  }
}

// 深层子组件消费配置
@Component
struct ButtonComponent {
  @Consume themeConfig: ThemeConfig
  
  build() {
    Button('提交')
      .backgroundColor(this.themeConfig.primaryColor)
      .fontSize(this.themeConfig.fontSize)
  }
}
  • 动态覆盖:通过allowOverride: true允许子组件重写祖先组件的@Provide状态,实现局部覆盖全局配置
@Component
struct Parent {
  @Provide({ allowOverride: true }) config = { theme: 'light' }
}

@Component
struct Child {
  @Provide config = { theme: 'dark' }  // 覆盖父级配置
}
  • 使用Map存储共享状态时需配合@Observed装饰器

2. @Observed与@ObjectLink

@Observed
class Product {
  constructor(public name: string, public stock: number) {}
}

// 父组件管理商品列表
@Component
struct ProductList {
  @State products: Product[] = [
    new Product('手机', 100),
    new Product('平板', 50)
  ]

  build() {
    Column() {
      ForEach(this.products, (item) => {
        ProductItem({ product: item })
      })
    }
  }
}

// 子组件修改库存
@Component
struct ProductItem {
  @ObjectLink product: Product

  build() {
    Row() {
      Text(`${this.product.name} 库存: ${this.product.stock}`)
      Button('-').onClick(() => this.product.stock--)
    }
  }
}

实现要点

  • 必须使用new创建@Observed类实例
  • 数组操作需保持引用变更(如this.products = [...this.products]

三、应用场景

1. @Provide/@Consume适用场景

  • 全局主题管理:跨层级组件共享UI主题配置
  • 用户登录状态:在任意子组件访问用户凭证
  • 多步骤表单:跨表单页共享填写数据

2. @Observed/@ObjectLink适用场景

  • 购物车系统:实时同步商品数量变更
  • 树形结构编辑:嵌套对象属性修改(如组织架构)
  • 表格数据操作:二维数组单元格内容更新

四、进阶技巧

1. 联合类型与复杂结构

@Provide user: User | null = null  // 支持联合类型
@Consume('user') currentUser: User | null

@Observed
class Order {
  items: Map<number, CartItem> = new Map()  // 支持Map类型
}

优化策略

  • 对大型Map使用ObjectLink时优先修改引用
  • 使用readonly修饰符减少代理开销

五、对比分析与选型指南

装饰器组合核心功能适用场景优缺点对比
@State + @Link父子组件单向/双向同步简单父子状态传递无法处理嵌套结构
@Provide + @Consume跨层级组件双向同步复杂组件树通信需要严格命名规范
@Observed + @ObjectLink嵌套对象/数组观察二维数组、嵌套类属性监听需要配合自定义类使用

关键差异

  • 作用范围:@Provide/@Consume作用于组件树层级,@Observed/@ObjectLink专注于数据结构深度。
  • 同步机制:前者依赖组件生命周期绑定,后者通过Proxy代理实现实时观测。

六、避坑指南

  1. 命名规范建议
    • 使用snake_case命名@Provide变量(如user_profile
    • 避免全局变量污染,优先使用别名隔离
  2. 性能优化:
    • 对大型数组/Map使用@Observed时,优先修改引用而非直接操作元素。
    • 使用readonly修饰不可变数据,减少不必要的代理开销。
  3. 常见错误处理
    • 未使用@Observed装饰类
    class User { ... }
    @ObjectLink user: User  // 运行时错误
    
    // 正确:
    @Observed class User { ... }
    
    • 确保@Consume变量在祖先组件中存在同名@Provide声明,避免运行时错误。

七、总结

  • @Provide/@Consume 是跨层级通信的"高速公路",适合全局状态共享(如主题切换、用户登录)
  • @Observed/@ObjectLink 是深度数据观察的"显微镜",专治嵌套结构更新难题(如订单详情、树形菜单)

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章