目录
- HarmonyOS Next 开发系列:Local 状态修饰器实践
- HarmonyOS Next 开发系列:Param 状态修饰器实践
- HarmonyOS Next 开发系列:Once 状态修饰器实践
- HarmonyOS Next 开发系列:Event 状态修饰器实践
- HarmonyOS Next 开发系列:!! 状态修饰器实践
- HarmonyOS Next 开发系列:@ObserverV2和@Trace状态修饰器实践
- HarmonyOS Next 开发系列:Provider和Consumer状态修饰器实践
一. @Provider 和 @Consumer 修饰器概述
在本系列之前介绍的状态修饰器中,无法直接实现跨层级的数据传递。而本次介绍的 @Provider
和 @Consumer
修饰器则能够实现跨组件的数据传递,并且支持双向数据绑定。
二. @Provider 和 @Consumer 修饰器的约束
- 使用范围 :
@Provider
和@Consumer
只能在ComponentV2
中使用,且必须成对出现。
- 数据提供方与消费方 :
@Provider
是数据的提供方,其所有子孙组件都可以通过@Consumer
绑定相同的 key 来获取@Provider
提供的数据。@Consumer
是数据的消费方,通过绑定相同的 key 获取其 最近父节点 的@Provider
数据。如果未找到对应的@Provider
,则使用 本地默认值。
- 数据更新机制 :
@Provider
和@Consumer
支持一对多的数据更新。- 当
@Provider
的数据发生变化时,所有绑定相同 key 的@Consumer
子孙组件都会同步更新。
- 支持修饰函数 :
@Provider
和@Consumer
支持修饰函数,类似于@Param
的用法。
- 初始化规则 :
@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
可以直接修饰基础类型(如 string
、number
、boolean
、enum
、null
、undefined
等),并在变量变化时更新关联的 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
不支持修饰unknown
和any
类型的数据。@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 修饰容器类型
以数组为例,其他容器类型(如 Array
、Tuple
、Set
、Map
)的行为类似。
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
来实现更复杂的状态管理需求。