@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。
@Provide/@Consume装饰的状态变量有以下特性:
- @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
- 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
- @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常。
@Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide装饰的变量和@Consume装饰的变量是一对多的关系。
被装饰变量的初始值
@Provide变量装饰器 必须指定。
@Consume变量装饰器 禁止本地初始化。
框架行为
-
初始渲染:
- @Provide装饰的变量会以map的形式,传递给当前@Provide所属组件的所有子组件;
- 子组件中如果使用@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的@Provide的变量,如果查找不到,框架会抛出JS ERROR;
- 在初始化@Consume变量时,和@State/@Link的流程类似,@Consume变量会在map中查找到对应的@Provide变量进行保存,并把自己注册给@Provide。
-
当@Provide装饰的数据变化时:
- 通过初始渲染的步骤可知,子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(@Consume);
- 通知@Consume更新后,子组件所有依赖@Consume的系统组件(elementId)都会被通知更新。以此实现@Provide对@Consume状态数据同步。
-
当@Consume装饰的数据变化时:
通过初始渲染的步骤可知,子组件@Consume持有@Provide的实例。在@Consume更新后调用@Provide的更新方法,将更新的数值同步回@Provide,以此实现@Consume向@Provide的同步更新。
使用场景
@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()
}
}
}
常见问题
@BuilderParam尾随闭包情况下@Provide未定义错误
class Tmp {
a: string = ''
}
@Entry
@Component
struct HomePage {
@Builder
builder2($$: Tmp) {
Text(`${$$.a}测试`)
}
build() {
Column() {
CustomWidget() {
CustomWidgetChild({ builder: this.builder2 })
}
}
}
}
@Component
struct CustomWidget {
@Provide('a') a: string = 'abc';
@BuilderParam
builder: () => void;
build() {
Column() {
Button('你好').onClick((x) => {
if (this.a == 'ddd') {
this.a = 'abc';
}
else {
this.a = 'ddd';
}
})
this.builder()
}
}
}
@Component
struct CustomWidgetChild {
@Consume('a') a: string;
@BuilderParam
builder: ($$: Tmp) => void;
build() {
Column() {
this.builder({ a: this.a })
}
}
}