HarmonyOS Next 开发系列:Provider和Consumer状态修饰器实践

目录

一. @Provider 和 @Consumer 修饰器概述

在本系列之前介绍的状态修饰器中,无法直接实现跨层级的数据传递。而本次介绍的 @Provider@Consumer 修饰器则能够实现跨组件的数据传递,并且支持双向数据绑定。

二. @Provider 和 @Consumer 修饰器的约束

  1. 使用范围
    • @Provider@Consumer 只能在 ComponentV2 中使用,且必须成对出现。
  2. 数据提供方与消费方
    • @Provider 是数据的提供方,其所有子孙组件都可以通过 @Consumer 绑定相同的 key 来获取 @Provider 提供的数据。
    • @Consumer 是数据的消费方,通过绑定相同的 key 获取其 最近父节点@Provider 数据。如果未找到对应的 @Provider,则使用 本地默认值
  3. 数据更新机制
    • @Provider@Consumer 支持一对多的数据更新。
    • @Provider 的数据发生变化时,所有绑定相同 key@Consumer 子孙组件都会同步更新。
  4. 支持修饰函数
    • @Provider@Consumer 支持修饰函数,类似于 @Param 的用法。
  5. 初始化规则
    • @Consumer 修饰的变量在初始化时,不需要通过创建子组件时显式赋值传递。
    • 如果存在多个同名的 @Provider@Consumer 会向上查找最近的 @Provider 进行数据绑定。

三. @Provider 和 @Consumer 的简单使用

示例一:绑定 Key(别名)

less 复制代码
@Entry
@ComponentV2
struct Index {
  @Provider('name') name: string = "孙膑"
  build() {

  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('name') cName: string = "子组件"
  build() {
  }
}

示例二:默认 Key(别名)

less 复制代码
@Entry
@ComponentV2
struct Index {
  @Provider() name: string = "孙膑"
  build() {

  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('name') cName: string = "子组件"
  build() {
  }
}

关键点:

  • 可选的 Key(别名) :Key 是可选参数,如果未设置,默认使用变量名作为 Key。
  • Key 的一致性:父子组件中绑定的 Key 必须保持一致。

四、实践探索

4.1 修饰基础类型

@Provider@Consumer可以直接修饰基础类型(如 stringnumberbooleanenumnullundefined 等),并在变量变化时更新关联的 UI 组件。

less 复制代码
enum Gender {
  Female,
  Male
}

@Entry
@ComponentV2
struct Index {
  @Provider() name: string = "孙膑"
  @Provider() age: number = 29
  @Provider() isMaimed: boolean = true
  @Provider() gender: Gender = Gender.Male
  @Provider() occupation: undefined | string = undefined
  @Provider() consort: null | string = null
  // @Provider() skillCnt: unknown = "skillCnt"
  // @Provider() skill: any = "skill"
  build() {
    Column({space: 5}) {
      Text(`name:${this.name}`)
      Text(`age:${this.age}`)
      Text(`isMaimed:${this.isMaimed}`)
      Text(`gender:${this.gender}`)
      Text(`occupation:${this.occupation}`)
      Text(`consort:${this.consort}`)
      Button('change value in Father component')
        .onClick(() => {
          this.name = (this.name == "孙膑" ? "庞涓" : "孙膑")
          this.age++
          this.isMaimed = !this.isMaimed
          this.gender = (this.gender == Gender.Female ? Gender.Male : Gender.Female)
          this.occupation = (this.occupation === undefined ? "occupation": undefined)
          this.consort = (this.consort === null ? "consort" : null)
        })

      ChildComponent()
    }
    .backgroundColor(Color.Yellow)
    .width('100%')
  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('name') cName: string = "赵云"
  @Consumer('age') cAge: number = 28
  @Consumer('isMaimed') cIsMaimed: boolean = false
  @Consumer('gender') cGender: Gender = Gender.Female
  @Consumer('occupation') cOccupation: undefined | string = "cUndefined"
  @Consumer('consort') cConsort: null | string = "cNull"
  build() {
    Column({space: 5}) {
      Text(`name:${this.cName}`)
      Text(`age:${this.cAge}`)
      Text(`isMaimed:${this.cIsMaimed}`)
      Text(`gender:${this.cGender}`)
      Text(`occupation:${this.cOccupation}`)
      Text(`consort:${this.cConsort}`)
      Button('change value in Child component')
        .onClick(() => {
          this.cName = (this.cName == "赵云" ? "马超" : "赵云")
          this.cAge++
          this.cIsMaimed = !this.cIsMaimed
          this.cGender = (this.cGender == Gender.Female ? Gender.Male : Gender.Female)
          this.cOccupation = (this.cOccupation === undefined ? "Child - occupation": undefined)
          this.cConsort = (this.cConsort === null ? "Child - consort" : null)
        })

      ItemComponent()
    }
    .backgroundColor(Color.Green)
  }
}

@ComponentV2
struct ItemComponent {
  @Consumer('name') iName: string = "吴邪"
  @Consumer('age') iAge: number = 25
  @Consumer('isMaimed') iIsMaimed: boolean = true
  @Consumer('gender') iGender: Gender = Gender.Male
  @Consumer('occupation') iOccupation: undefined | string = undefined
  @Consumer('consort') iConsort: null | string = null
  build() {
    Column({space: 5}) {
      Text(`name:${this.iName}`)
      Text(`age:${this.iAge}`)
      Text(`isMaimed:${this.iIsMaimed}`)
      Text(`gender:${this.iGender}`)
      Text(`occupation:${this.iOccupation}`)
      Text(`consort:${this.iConsort}`)

      Button('change value in Item component')
        .onClick(() => {
          this.iName = (this.iName == "吴邪" ? "张起灵" : "吴邪")
          this.iAge++
          this.iIsMaimed = !this.iIsMaimed
          this.iGender = (this.iGender == Gender.Female ? Gender.Male : Gender.Female)
          this.iOccupation = (this.iOccupation === undefined ? "Item - occupation": undefined)
          this.iConsort = (this.iConsort === null ? "Item - consort" : null)
        })

      ElementComponent()
    }
    .backgroundColor(Color.Gray)
  }
}

@ComponentV2
struct ElementComponent {
  @Consumer('name') eName: string = "大乔"
  @Consumer('age') eAge: number = 18
  @Consumer('isMaimed') eIsMaimed: boolean = true
  @Consumer('gender') eGender: Gender = Gender.Female
  @Consumer('occupation') eOccupation: undefined | string = undefined
  @Consumer('consort') eConsort: null | string = null
  build() {
    Column({space: 5}) {
      Text(`name:${this.eName}`)
      Text(`age:${this.eAge}`)
      Text(`isMaimed:${this.eIsMaimed}`)
      Text(`gender:${this.eGender}`)
      Text(`occupation:${this.eOccupation}`)
      Text(`consort:${this.eConsort}`)
      Button('change value in Element component')
        .onClick(() => {
          this.eName = (this.eName == "大乔" ? "小乔" : "大乔")
          this.eAge++
          this.eIsMaimed = !this.eIsMaimed
          this.eGender = (this.eGender == Gender.Female ? Gender.Male : Gender.Female)
          this.eOccupation = (this.eOccupation === undefined ? "Element - occupation": undefined)
          this.eConsort = (this.eConsort === null ? "Element - consort" : null)
        })
    }
    .backgroundColor(Color.Pink)
  }
}

关键点:

  • @Provider@Consumer 不支持修饰 unknownany 类型的数据。
  • @Consumer 的本地默认值会被层级树中最临近父节点@Provider 覆盖。
  • @Provider@Consumer 可以实现跨层级组件间的双向数据传递,从而刷新与之关联的多级组件 UI。

4.2 修饰对象类型

less 复制代码
@ObservedV2
class UserInfo {
  @Trace name: string
  @Trace age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

class Address {
  country: string
  code: number
  constructor(country: string, code: number) {
    this.country = country
    this.code = code
  }
}


@Entry
@ComponentV2
struct Index {
  @Provider() userInfo: UserInfo = new UserInfo("孙膑", 28)
  @Provider() address: Address = new Address("齐国", 798)
  build() {
    Column({space: 5}) {
      Row() {
        Text(`name:${this.userInfo.name}`)
        Text(`age:${this.userInfo.age}`)
      }
      .backgroundColor(Color.Orange)
      Row() {
        Text(`country:${this.address.country}, code:${this.address.code}`)
      }
      .backgroundColor(Color.Green)
      Button('change object in Father component')
        .onClick(() => {
          this.userInfo = new UserInfo('庞涓', 31)
          this.address = new Address('魏国', 573)
        })
      Button('change property in Father component')
        .onClick(() => {
          this.userInfo.age++
          this.address.code++
        })

      Line().width('100%').height(1).backgroundColor(Color.Gray)

      ChildComponent()
    }
    .width('100%')
  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('userInfo') cUserInfo: UserInfo = new UserInfo("庞涓", 31)
  @Consumer('address') cAddress: Address = new Address("魏国", 777)

  build() {
    Column({space: 5}) {
      Row() {
        Text(`name:${this.cUserInfo.name}`)
        Text(`age:${this.cUserInfo.age}`)
      }
      .backgroundColor(Color.Orange)
      Row() {
        Text(`country:${this.cAddress.country}, code:${this.cAddress.code}`)
      }
      .backgroundColor(Color.Green)
      Button('change value in Child component')
        .onClick(() => {
          this.cUserInfo = new UserInfo('马超', 27)
          this.cAddress = new Address('蜀国', 143)
        })
      Button('change property in Child component')
        .onClick(() => {
          this.cUserInfo.age++
          this.cAddress.code++
        })
    }
  }
}

关键点:

  • @Provider@Consumer 修饰对象类型时,只有在对象引用发生变化时才会触发 UI 刷新。对象属性的变化不会触发 UI 刷新。
  • 结合 @ObservedV2@Trace 可以实现对对象类型和对象属性变化的监听,从而触发 UI 刷新。

4.3 修饰容器类型

以数组为例,其他容器类型(如 ArrayTupleSetMap)的行为类似。

typescript 复制代码
class UserInfo {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

class Address {
  country: string
  code: number
  constructor(country: string, code: number) {
    this.country = country
    this.code = code
  }
}


@Entry
@ComponentV2
struct Index {
  @Provider() userInfos: UserInfo[] = [new UserInfo("孙膑", 28)]
  @Provider() addresses: Address[] = [new Address("齐国", 798)]
  build() {
    Column({space: 5}) {
      ForEach(this.userInfos, (userInfo: UserInfo) => {
        Row() {
          Text(`name:${userInfo.name}`)
          Text(`age:${userInfo.age}`)
        }
      })
      ForEach(this.addresses, (address: Address) => {
        Row() {
          Text(`country:${address.country}`)
          Text(`code:${address.code}`)
        }
      })
      Button('change list in Father component')
        .onClick(() => {
          this.userInfos.push(new UserInfo('赵云', 22))
          this.addresses.push(new Address('蜀国', 666))
        })
      Button('change object in Father component')
        .onClick(() => {
          this.userInfos[0].name += '父组件'
          this.addresses[0].code++
        })

      Line().width('100%').height(1).backgroundColor(Color.Gray)

      ChildComponent()
    }
    .width('100%')
  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('userInfos') cUserInfos: UserInfo[] = [new UserInfo("庞涓", 31)]
  @Consumer('addresses') cAddresses: Address[] = [new Address("魏国", 777)]

  build() {
    Column({space: 5}) {
      ForEach(this.cUserInfos, (userInfo: UserInfo) => {
        Row() {
          Text(`name:${userInfo.name}`)
          Text(`age:${userInfo.age}`)
        }
      })
      ForEach(this.cAddresses, (address: Address) => {
        Row() {
          Text(`country:${address.country}`)
          Text(`code:${address.code}`)
        }
      })
      Button('change list in Child component')
        .onClick(() => {
          this.cUserInfos.push(new UserInfo('小乔', 18))
          this.cAddresses.push(new Address('吴国', 679))
        })
      Button('change object in Child component')
        .onClick(() => {
          this.cUserInfos[0].name += '自福建'
          this.cAddresses[0].code += 2
        })
    }
  }
}

关键点:

  • @Provider@Consumer 修饰容器类型时,能够检测到容器的变化,从而刷新容器对象关联的 UI。对于容器内对象属性的变化无法直接检测。
  • 结合 @ObservedV2@Trace 可以实现对容器内对象属性变化的监听。

4.4 修饰可选类型

kotlin 复制代码
class UserInfo {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

@Entry
@ComponentV2
struct Index {
  @Provider() userInfo?: UserInfo
  @Provider() msg?: string

  build() {
    Column({space: 5}) {
      Text(`name:${this.userInfo?.name}, age:${this.userInfo?.age}`)
      Text(`msg:${this.msg}`)
      Button('change object in Father component')
        .onClick(() => {
          this.userInfo = this.userInfo === undefined ? new UserInfo('孙膑', 28) : undefined
          this.msg = "父组件"
        })
      Button('change property in Father component')
        .onClick(() => {
          this.userInfo && (this.userInfo.name = "庞涓")
          this.msg += "abc"
        })

      Line().width('100%').height(1).backgroundColor(Color.Gray)

      ChildComponent()
    }
    .width('100%')
  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('userInfo') cUserInfo?: UserInfo
  @Consumer('msg') cMsg?: string

  build() {
    Column({space: 5}) {
      Text(`name:${this.cUserInfo?.name}, age:${this.cUserInfo?.age}`)
      Text(`msg:${this.cMsg}`)
      Button('change list in Child component')
        .onClick(() => {
          this.cUserInfo = this.cUserInfo === undefined ? new UserInfo('小乔', 18) : undefined
          this.cMsg = "子组件"
        })
      Button('change object in Child component')
        .onClick(() => {
          this.cUserInfo && (this.cUserInfo.name = "小乔")
          this.cMsg += "123"
        })
    }
  }
}

关键点:

  • @Provider@Consumer 可以修饰可选类型的值,且默认初始化为 undefined
  • 以上用法可以推广到多层组件中使用。

4.5 修饰function类型

less 复制代码
@Entry
@ComponentV2
struct Index {
  @Local childX: number = 0
  @Local childY: number = 0
  @Provider() onTouchPoint: (x: number, y: number) => void = (x: number, y: number) => {
    this.childX = x
    this.childY = y
  }

  build() {
    Column({space: 5}) {
      Text(`child touch: x:${this.childX}, y:${this.childY}`)

      Line().width('100%').height(1).backgroundColor(Color.Gray)

      ChildComponent()
    }
    .width('100%')
  }
}

@ComponentV2
struct ChildComponent {
  @Consumer('onTouchPoint') cOnTouchPoint: (x: number, y: number) => void = (x: number, y: number) => {}
  build() {
    Column({space: 5}) {
      Button('Child')
        .onClick((event) => {
          this.cOnTouchPoint(event.displayX, event.displayY)
        })
    }
  }
}

关键点

  • @Provider@Consumer 支持修饰函数类型,适用于跨组件的事件传递。

4.6 重名Provider的场景

scss 复制代码
@Entry
@ComponentV2
struct Index {
  @Provider() msg: string = "父组件"
  build() {
    Column({space: 5}) {
      Text(`msg:${this.msg}`)
      Button('In Father')
        .onClick(() => {
          this.msg += "父组件"
        })

      Line().width('100%').height(1).backgroundColor(Color.Gray)

      ChildComponent()
    }
    .width('100%')
  }
}

@ComponentV2
struct ChildComponent {
  @Provider('msg') cMsg: string = "子组件"
  build() {
    Column({space: 5}) {
      Text(`子组件:${this.cMsg}`)
      Button('In Child')
        .onClick(() => {
          this.cMsg += "子组件"
        })

      Line().width('100%').height(1).backgroundColor(Color.Gray)

      ItemComponent()
    }
  }
}

@ComponentV2
struct ItemComponent {
  @Consumer('msg') iMsg: string = "元素组件"
  build() {
    Column({space: 5}) {
      Text(`元素组件:${this.iMsg}`)
      Button('In Item')
        .onClick(() => {
          this.iMsg += "元素组件"
        })
    }
  }
}

关键点:

  • 子孙组件的 @Consumer 会向上查找最近的 @Provider 进行数据绑定。
  • 其余 @Provider 装饰的变量的更新会触发其所在层级组件的 UI 刷新,但不会更新子孙组件的数据。

五、总结

@Provider@Consumer 提供了一种灵活的方式来实现跨组件的数据传递和双向绑定。通过合理使用这些修饰器,可以显著提升组件的复用性和可维护性。在实际开发中,建议根据具体场景选择合适的数据传递方式,并结合 @ObservedV2@Trace 来实现更复杂的状态管理需求。

相关推荐
别说我什么都不会1 小时前
当OpenHarmony遇上OpenEuler
操作系统·嵌入式·harmonyos
没有了遇见1 小时前
鸿蒙学习ArkTS之工程目录说
harmonyos
没有了遇见1 小时前
鸿蒙学习ArkTS之Module创建和引用
harmonyos
万少2 小时前
喜大普奔 DevEco Studio 官方接 入 DeepSeek 了
人工智能·harmonyos·ai 编程
遇到困难睡大觉哈哈3 小时前
HarmonyOS 音频录制与播放模块
华为·音视频·harmonyos·鸿蒙
IT乐手4 小时前
2.2、层叠布局(Stack)
harmonyos
IT乐手4 小时前
2.1、线性布局(Row/Column)
harmonyos
SunshineBrother5 小时前
shell脚本,怎么查找项目中的重复图片
ios
Georgewu5 小时前
【HarmonyOS Next】鸿蒙加固方案调研和分析
前端·面试·harmonyos