文章大纲
- 引言
- 一、@Provide和@Consume装饰器概述
- 1、@Provide和@Consume关系的绑定
- 2、使用规则
- 3、变量的传递/访问规则
- 4、支持的观察变化的场景
- 5、@Provide和@Consume变量的值初始化和更新机制
- 5.1、初始渲染
- 5.2、当@Provide装饰的数据变化时:
- 5.3、当@Consume装饰的数据变化时
- 三、简单应用
- 四、利用@Provide 和@Consume 传递回调接口
- 1、@Provide 定义要传递的回调接口
- 2、@Consume 定义使用这个回调接口
引言
前面添加链接描述OpenHarmony 入门——ArkUI 自定义组件间的父子双向同步状态装饰器@Link语法(四)介绍了父子组件间的双向同步@Link的使用方法,只能是在单层级上传递,而今天介绍来的这组是可以在多层级之间传递的@Provide和@Consume装饰器。
此处的后代并非传统意义上的后代继承关系,仅仅是代表一种认为约定的关系,所有使用@Provide装饰器的都可以认为是其后代,更贴切些应该是“生产-消费”模型装饰器。
一、@Provide和@Consume装饰器概述
应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
其中**@Provide装饰的变量是在祖先组件中,即“提供”给后代的状态变量,@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供**的变量。
-
@Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
-
后代通过使用**@Consume去获取@Provide提供的变量**,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
1、@Provide和@Consume关系的绑定
@Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
@Provide装饰的变量和@Consume装饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量,@Provide的属性名或别名需要唯一且确定,如果声明多个同名或者同别名的@Provide装饰的变量,会发生运行时报错。即先通过@Provide 定义一个所谓的 祖先变量,然后再在其他组件里定义一个@Consume 的 后代变量,那么只要祖先变量值改变时都可以通知到所有绑定了这个后代变量的组件的,完成数据同步,轻松实现跨组件。
2、使用规则
@State的规则同样适用于@Provide,不同的是@Provide还作为多层后代的同步源。
3、变量的传递/访问规则
4、支持的观察变化的场景
- 数据类型为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()
}
}
}
5、@Provide和@Consume变量的值初始化和更新机制
5.1、初始渲染
- @Provide装饰的变量会以map的形式,传递给当前@Provide所属组件的所有子组件;
- 子组件中如果使用@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的@Provide的变量,如果查找不到,框架会抛出JS ERROR;
- 在初始化@Consume变量时,和@State/@Link的流程类似,@Consume变量会保存在map中查找到的@Provide变量,并把自己注册给@Provide。
5.2、当@Provide装饰的数据变化时:
- 通过初始渲染的步骤可知,子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(@Consume);
- 通知@Consume更新后,子组件所有依赖@Consume的系统组件(elementId)都会被通知更新。以此实现@Provide对@Consume状态数据同步。
5.3、当@Consume装饰的数据变化时
- 通过初始渲染的步骤可知,子组件@Consume持有@Provide的实例。
- 在@Consume更新后调用@Provide的更新方法,将更新的数值同步回@Provide,以此实现@Consume向@Provide的同步更新。
三、简单应用
@Component
struct ConsumeCompA {
// @Consume装饰的变量通过相同的属性名绑定其祖先组件CompA内的@Provide装饰的变量
@Consume reviewVotes: number;
build() {
Column() {
Button(`ConsumeCompA @Provide变量+3 后: ${this.reviewVotes}`)
.onClick(() => this.reviewVotes += 3)
}
.width('50%')
}
}
@Component
struct ConsumeCompC {
build() {
Row({ space: 5 }) {
ConsumeCompA()
ConsumeCompA()
}
}
}
@Component
struct ConsumeCompB {
build() {
ConsumeCompC()
}
}
@Entry
@Component
struct ProvideComp {
// @Provide装饰的变量reviewVotes由入口组件ProvideComp提供其后代组件
@Provide reviewVotes: number = 68;
build() {
Column() {
Button(`ProvideComp @Provide 变量 + 2后 :${this.reviewVotes}`)
.onClick(() => this.reviewVotes += 2).width('100%')
Divider().height(20)
ConsumeCompB()
Divider().height(20)
ConsumeCompA().backgroundColor('#ff0000').width('100%')
}
}
}
四、利用@Provide 和@Consume 传递回调接口
1、@Provide 定义要传递的回调接口
@Provide 注解用于提供一个可以在子组件中注入的值,在这里是一个 dialogCallback 对象,包含两个回调函数:confirmCallback 和 cancelCallback。
export interface DialogCallback {
confirmCallback: Function;
cancelCallback: Function;
}
@Component
export struct CustomDialogView {
@Provide dialogCallback: DialogCallback = { confirmCallback: (): void => {}, cancelCallback: () => {} };
private showDeleteDialog(deleteMessage: Resource, confirmCallback: Function, cancelCallback: Function): void {
this.dialogCallback = { confirmCallback: confirmCallback, cancelCallback: cancelCallback as Function };
}
private showRemoveDialog(removeMessage: Resource, confirmCallback: Function, cancelCallback?: Function): void {
this.dialogCallback = { confirmCallback: confirmCallback, cancelCallback: cancelCallback as Function };
}
}
showDeleteDialog 方法用于展示删除对话框,接收一个资源字符串 deleteMessage 和两个回调函数作为参数,设置 dialogMessage 和 dialogCallback 的值。
2、@Consume 定义使用这个回调接口
@Consume 注解用于消费由父组件提供的值,在这里消费的是 dialogCallback。当DeleteDialog 被 CustomDialogView 使用,当需要展示删除确认对话框时,CustomDialogView 将通过 showDeleteDialog 方法来调用 DeleteDialog
@CustomDialog
export struct DeleteDialog {
@Consume dialogCallback: DialogCallback;
build() {
Column() {
Button()
.key('DeleteDialogConfirmButton')
.onClick(() => {
this.dialogCallback && this.dialogCallback.confirmCallback();
})
}
}
}
RemoveDialog
@CustomDialog
export struct RemoveDialog {
@Consume dialogCallback: DialogCallback;
build() {
Column() {
Button()
.key('DeleteDialogConfirmButton')
.onClick(() => {
this.dialogCallback && this.dialogCallback.confirmCallback();
})
}
}
}