openharmony后代组件双向同步,跨层级传递:@provide装饰器和@consume装饰器
@provide和@consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@provide和@consume摆脱参数传递机制的束缚,实现跨层级传递。
其中@provide装饰的变量是在祖先节点中,可以理解为被“提供”给后代的状态变量。@consume装饰的变量是在后代组件中,去“消费(绑定)”祖先节点提供的变量。
说明:
从api version 9开始,这两个装饰器支持在arkts卡片中使用。
概述
@provide/@consume装饰的状态变量有以下特性:
● @provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@provide的方便之处在于,开发者不需要多次在组件之间传递变量。
● 后代通过使用@consume去获取@provide提供的变量,建立在@provide和@consume之间的双向数据同步,与@state/@link不同的是,前者可以在多层级的父子组件之间传递。
● @provide和@consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
// 通过相同的变量名绑定
@provide a: number = 0;
@consume a: number;
// 通过相同的变量别名绑定
@provide('a') b: number = 0;
@consume('a') c: number;
@provide和@consume通过相同的变量名或者相同的变量别名绑定时,@provide修饰的变量和@consume修饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@provide装饰的变量,@provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@provide装饰的变量,会发生运行时报错。
装饰器说明
@state的规则同样适用于@provide,差异为@provide还作为多层后代的同步源。
@provide变量装饰器 | 说明 |
---|---|
装饰器参数 | 别名:常量字符串,可选。如果指定了别名,则通过别名来绑定变量;如果未指定别名,则通过变量名绑定变量。 |
同步类型 | 双向同步。从@provide变量到所有@consume变量以及相反的方向的数据同步。双向同步的操作与@state和@link的组合相同。 |
允许装饰的变量类型 | object、class、string、number、boolean、enum类型,以及这些类型的数组。支持date类型。支持类型的场景请参考观察变化。不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。必须指定类型。@provide变量的@consume变量的类型必须相同。说明:不支持length、resourcestr、resourcecolor类型,length、resourcestr、resourcecolor为简单类型和复杂类型的联合类型。 |
被装饰变量的初始值 | 必须指定。 |
@consume变量装饰器 | 说明 |
---|---|
装饰器参数 | 别名:常量字符串,可选。如果提供了别名,则必须有@provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。 |
同步类型 | 双向:从@provide变量(具体请参见@provide)到所有@consume变量,以及相反的方向。双向同步操作与@state和@link的组合相同。 |
允许装饰的变量类型 | object、class、string、number、boolean、enum类型,以及这些类型的数组。支持date类型。支持类型的场景请参考观察变化。不支持any,不允许使用undefined和null。必须指定类型。@provide变量的@consume变量的类型必须相同。说明:@consume装饰的变量,在其父节点或者祖先节点上,必须有对应的属性和别名的@provide装饰的变量。 |
被装饰变量的初始值 | 无,禁止本地初始化。 |
变量的传递/访问规则说明
@provide传递/访问 | 说明 |
---|---|
从父组件初始化和更新 | 可选,允许父组件中常规变量(常规变量对@prop赋值,只是数值的初始化,常规变量的变化不会触发ui刷新,只有状态变量才能触发ui刷新)、@state、@link、@prop、@provide、@consume、@objectlink、@storagelink、@storageprop、@localstoragelink和@localstorageprop装饰的变量装饰变量初始化子组件@provide。 |
用于初始化子组件 | 允许,可用于初始化@state、@link、@prop、@provide。 |
和父组件同步 | 否。 |
和后代组件同步 | 和@consume双向同步。 |
是否支持组件外访问 | 私有,仅可以在所属组件内访问。 |
图1 @provide初始化规则图示
@consume传递/访问 | 说明 |
---|---|
从父组件初始化和更新 | 禁止。通过相同的变量名和alias(别名)从@provide初始化。 |
用于初始化子组件 | 允许,可用于初始化@state、@link、@prop、@provide。 |
和祖先组件同步 | 和@provide双向同步。 |
是否支持组件外访问 | 私有,仅可以在所属组件内访问 |
图2 @consume初始化规则图示
观察变化和行为表现
观察变化
● 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
● 当装饰的数据类型为class或者object的时候,可以观察到赋值和属性赋值的变化(属性为object.keys(observedobject)返回的所有属性)。
● 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。
● 当装饰的对象是date时,可以观察到date整体的赋值,同时可通过调用date的接口setfullyear, setmonth, setdate, sethours, setminutes, setseconds, setmilliseconds, settime, setutcfullyear, setutcmonth, setutcdate, setutchours, setutcminutes, setutcseconds, setutcmilliseconds 更新date的属性。
@component
struct compd {
@consume selecteddate: date;
build() {
column() {
button(`child increase the day by 1`)
.onclick(() => {
this.selecteddate.setdate(this.selecteddate.getdate() + 1)
})
button('child update the new date')
.margin(10)
.onclick(() => {
this.selecteddate = new date('2023-09-09')
})
datepicker({
start: new date('1970-1-1'),
end: new date('2100-1-1'),
selected: this.selecteddate
})
}
}
}
@entry
@component
struct compa {
@provide selecteddate: date = new date('2021-08-08')
build() {
column() {
button('parent increase the day by 1')
.margin(10)
.onclick(() => {
this.selecteddate.setdate(this.selecteddate.getdate() + 1)
})
button('parent update the new date')
.margin(10)
.onclick(() => {
this.selecteddate = new date('2023-07-07')
})
datepicker({
start: new date('1970-1-1'),
end: new date('2100-1-1'),
selected: this.selecteddate
})
compd()
}
}
}
框架行为
1. 初始渲染:
a. @provide装饰的变量会以map的形式,传递给当前@provide所属组件的所有子组件;
b. 子组件中如果使用@consume变量,则会在map中查找是否有该变量名/alias(别名)对应的@provide的变量,如果查找不到,框架会抛出js error;
c. 在初始化@consume变量时,和@state/@link的流程类似,@consume变量会保存在map中查找到的@provide变量,并把自己注册给@provide。
2. 当@provide装饰的数据变化时:
a. 通过初始渲染的步骤可知,子组件@consume已把自己注册给父组件。父组件@provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(@consume);
b. 通知@consume更新后,子组件所有依赖@consume的系统组件(elementid)都会被通知更新。以此实现@provide对@consume状态数据同步。
3. 当@consume装饰的数据变化时:
通过初始渲染的步骤可知,子组件@consume持有@provide的实例。在@consume更新后调用@provide的更新方法,将更新的数值同步回@provide,以此实现@consume向@provide的同步更新。
使用场景
在下面的示例是与后代组件双向同步状态@provide和@consume场景。当分别点击compa和compd组件内button时,reviewvotes 的更改会双向同步在compa和compd中。
@component
struct compd {
// @consume装饰的变量通过相同的属性名绑定其祖先组件compa内的@provide装饰的变量
@consume reviewvotes: number;
build() {
column() {
text(`reviewvotes(${this.reviewvotes})`)
button(`reviewvotes(${this.reviewvotes}), give +1`)
.onclick(() => this.reviewvotes += 1)
}
.width('50%')
}
}
@component
struct compc {
build() {
row({ space: 5 }) {
compd()
compd()
}
}
}
@component
struct compb {
build() {
compc()
}
}
@entry
@component
struct compa {
// @provide装饰的变量reviewvotes由入口组件compa提供其后代组件
@provide reviewvotes: number = 0;
build() {
column() {
button(`reviewvotes(${this.reviewvotes}), give +1`)
.onclick(() => this.reviewvotes += 1)
compb()
}
}
}
发表评论