HarmonyOS-ArkUI V2装饰器: @Provider和@Consumer装饰器:跨组件层级双向同步
Provider需要与@Consumer配合使用。两者通过一个key建立联结。此key表现在,这两个装饰器都接收一个叫aliasName的参数,用来指定其修饰的变量的别名。之后靠这个别名来寻找绑定关系。@Provider属性装饰器说明装饰器参数aliasName?:string, 别名,缺省时默认为属性名,建议最好写这个参数支持类型自定义组件中成员变量。属性类型可以为number,string,
作用
我们在之前学习的那些控件中,各有特点,也各有缺陷,至今没有痛痛快快的出现过真正能跨组件的双向绑定的装饰器。
比如
- @Local装饰器,不能跨组件
- @Param装饰器呢,能跨组件传递,但是仅仅就是下一层组件接收参数。另外,它是单向传递,不可被重新赋值。如果您非要改值则用@Once修饰,改了父组件也不会同步。非要达成双向传递的效果,那就搞个@Event写回调函数,让父组件实现,完成改值的能力。比较麻烦。
我们有时候需要一种能力是,对于组件而言。爷爷组件直接传给孙子组件,父组件不需要某状态变量。并且孙子组件如果改了值,或者爷爷组件改了值,双向同步给彼此。从而是界面刷新逻辑正常。
这个@Provider和@Consumer装饰器的配合使用,就是来完成这种效果的。
使用方式
介绍
@Provider需要与@Consumer配合使用。两者通过一个key建立联结。此key表现在,这两个装饰器都接收一个叫aliasName的参数,用来指定其修饰的变量的别名。之后靠这个别名来寻找绑定关系。
|
@Provider属性装饰器 |
说明 |
|
装饰器参数 |
aliasName?:string, 别名,缺省时默认为属性名,建议最好写这个参数 |
|
支持类型 |
自定义组件中成员变量。属性类型可以为number,string, boolean, class, Array, Date, Map, Set 等类型。支持装饰箭头函数。 |
|
从父组件初始化 |
禁止 |
|
本地初始化 |
必须本地初始化 |
|
观察能力 |
能力等同于@Trace。变化同步给对应的@Consumer |
|
@Consumer属性装饰器 |
说明 |
|
装饰器参数 |
aliasName?:string, 别名,缺省时默认为属性名,向上查找最近的@Provider,建议最好写这个参数 |
|
可装饰的变量 |
自定义组件中的成员变量。属性的类型可以是number,string,boolean,Data,Array,Map,Set等类型,支持箭头函数。 |
|
从父组件初始化 |
禁止 |
|
本地初始化 |
必须本地初始化 |
|
观察能力 |
能力等同于@Trace。变化会同步给对应的@Provider。 |
上述提到的Data,Array,Map,Set等类型,可以观测到的是API变化,具体如下:
|
类型 |
可观测变化的API |
|
Array |
push、pop、shift、unshift、splice、copyWithin、fill、reverse、sort |
|
Date |
setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds |
|
Map |
set, clear, delete |
|
Set |
add, clear, delete |
双向绑定使用案例
简单使用场景:
@Entry
@ComponentV2
struct ProviderConsumerTest {
@Provider() str: string = 'hello'
build() {
Column() {
Button(this.str)
.onClick(()=>{
this.str += 0
})
Child2()
}
.height('100%')
.width('100%')
}
}
@ComponentV2
struct Child2{
@Consumer() str:string = "world" //没写参数,则意味着要找父组件中叫 str 的变量
build() {
Column(){
Button(this.str)
.onClick(()=>{
this.str += 0
})
}
}
}
展示界面:

注意
关于aliasName设置上会出现的问题
上方表格里提到了aliasName,我们在代码里,仔细看的话,发现没有填这个aliasName。 因为他们都是有个问号,也就是其实用户在使用的时候可以填入也可以不填入。那么现实中很可能出现
- Provider填了,Consume没填
- Provider没填,Consumer没填
- Provider填了,Consumer填了
- Provider没填,Consumer填了
看起来挺乱的,我们之前讲过,这两个是靠这个aliasName寻找来绑定的。寻找规则为
- 两者都是,装饰器修饰的时候没有传入aliasName,那么全部按照其变量名当做aliasName,寻找彼此。
- 上方代码是父组件变量名是str,子组件也是str, 这样两个寻找的时候都按照str这个名字找的,能找得到。但是如果您但凡把父控件改成str1,或者其他的名字。或者子组件也改了名字,照这样写代码,肯定会出现找不到彼此这种问题,从而引发逻辑错误。这样双向绑定就错了!
- 综上所述,为维护代码的健壮性和稳定性,建议用Provider和Consumer的时候加上aliasName!您要是不写,别人在维护您代码的时候改了个变量名,就会引起不必要的bug,浪费时间。
下方是当父组件与子组件变量名称不一样的时候的最佳写法
![]()
太多同名Provider(aliasName)时,Consumer的查找逻辑
@Provider在组件树上是可以重名的,@Consumer会向上查找其最近父节点的@Provider数据。
案例:
@Entry
@ComponentV2
struct ProviderConsumerTest {
@Provider() val: number = 10;
build() {
Column() {
Parent()
}
.backgroundColor(Color.Orange)
.width('100%')
.height('50%')
}
}
@ComponentV2
struct Parent {
@Provider() val: number = 20;
@Consumer("val") val2: number = 0; //用ProviderConsumerTest中的 10
build() {
Column() {
Text(`${this.val2}`)
Child()
}
.backgroundColor(Color.Pink)
}
}
@ComponentV2
struct Child {
@Consumer() val: number = 0; //用Parent组件中的 20
build() {
Column() {
Text(`${this.val}`)
}
.backgroundColor(Color.Yellow)
}
}

上面的例子中:
- Parent中@Consumer向上查找,查找到ProviderConsumerTest中定义的 @Provider() val: number = 10,所以初始化为10。
- Child中@Consumer向上查找,查找到Parent中定义的@Provider() val: number = 20后停止,不会继续向上查找,所以初始化为20。
使用场景
@Provider与@Consumer修饰回调事件
这两个装饰器也能修饰回调事件。如果您看了之前的文章的话,可能会回想起来@Event不是也是修饰回调的么?文章链接: 25041103-ArkUI V2装饰器-@Event:规范组件输出。
但是这里面有一个比较本质的区别!就是,@Provider与@Consumer是可以跨组件传递的。而且@Consumer修饰的子组件,在使用的时候根本不用配置@Consumer修饰的状态变量为什么。 我们可以想一个场景,就是爷爷组件,直接给孙子组件,跨过父组件。@Event是做不到的, 它得由爷爷传给父亲,再由父亲传给孙子!这是这里面比较重要的区别。 @Provider与@Consumer最最最重要的就是跨组件中的”跨“!
代码:
@Entry
@ComponentV2
struct ProviderConsumerTest{
@Local fontColor: Color = Color.Red
@Provider() changeColor: (color: Color) => void = (color: Color) => {
this.fontColor = color
}
build() {
Column(){
Text(`颜色`)
.fontColor(this.fontColor)
Child3()
}
.width('100%')
.height('100%')
}
}
@ComponentV2
struct Child3 {
@Consumer() changeColor: (color: Color) => void = (color: Color) => {};
build() {
Button("changed")
.onClick(()=>{
this.changeColor(Color.Green);
})
}
}

@Provider和@Consumer装饰复杂类型,配合@Trace一起使用
@Provider和@Consumer只能观察到数据本身的变化。对于类结构上的深层观测能力是不具备的。所以要借助@ObservedV2和@Trace一起使用。
@ObservedV2
class User {
@Trace name: string;
@Trace age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const data: User[] = [new User('Json', 10), new User('Eric', 15)];
@Entry
@ComponentV2
struct Parent {
@Provider('data') users: User[] = data;
build() {
Column() {
Child()
Button('add new user')
.onClick(() => {
this.users.push(new User('Molly', 18));
})
Button('age++')
.onClick(() => {
this.users[0].age++;
})
Button('change name')
.onClick(() => {
this.users[0].name = 'Shelly';
})
}
}
}
@ComponentV2
struct Child {
@Consumer('data') users: User[] = [];
build() {
Column() {
ForEach(this.users, (item: User) => {
Column() {
Text(`name: ${item.name}`).fontSize(30)
Text(`age: ${item.age}`).fontSize(30)
Divider()
}
})
}
}
}
@Provider和@Consumer初始化@Param 传递链路分析
Provider修饰的状态变量是可以传值给Param的。
@Entry
@ComponentV2
struct ProviderConsumerTest{
@Provider() val: number = 10
build() {
Column(){
Parent({valParam: this.val})
}
}
}
@ComponentV2
struct Parent{
@Consumer() val: number = 0 //接收ProviderConsumerTest组件的 val
@Param valParam: number = 0
build() {
Column(){
Text(`Parent @Consumer val: ${this.val}`).fontSize(30).onClick(() => {
this.val++;
})
Text(`Parent @Param val2: ${this.valParam}`).fontSize(30)
Child4({ val: this.val })
}
}
}
@ComponentV2
struct Child4{
@Param val:number = 0
build() {
Column() {
Text(`Child @Param val ${this.val}`).fontSize(30)
}
.border({ width: 2, color: Color.Pink })
}
}

上述代码中的状态变量变化传递链路:

使用限制
- @Provider和@Consumer属于状态管理V2装饰器,所以只能在@ComponentV2组件中才能使用。
- @Provider和@Consumer是自定义组件装饰器,只能用在自定义组件struct里,而class中不可以。
- @Provider和@Consumer仅支持本地初始化,不支持外部组件传入。因为这俩装饰器已经使值跨组件透传了,不需要再自己专门写属性以初始化的形式传。
更多推荐
所有评论(0)