鸿蒙UI开发快速入门 —— part08: 组件状态管理之@Provide/@Consume装饰器
1、说在前面的话
在此之前,我们已经先后学习了三个装饰器:@State、@Props、@Link,它们的功能和使用场景分别是什么?暂停会议一下。
我们目前已经可以处理组件内状态(@State),也可以处理父组件向子组件传递状态(@Props),还可以处理父组件与子组件共用状态(@Link)。
我们再想一种场景:如果有一个状态,有非常多个子组件都需要共用,同时,子组件的层级可能会不止一层,此时,如果我们想做到类似的效果的话,则需要为每个层级都传递一遍@Props,这将是灾难。
为了解决多层级的状态同步问题,鸿蒙引入了 @Provide装饰器和@Consume装饰器。
@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。
其中@Provide装饰的变量是在祖先节点中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先节点提供的变量。
接下来我们详细了解他们。
2、@Provide装饰器和@Consume装饰器
@Provide/@Consume装饰的状态变量有以下特性:
-
@Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
-
后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
-
@Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,变量类型必须相同。
-
@Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide修饰的变量和@Consume修饰的变量是一对多的关系。不允许在同一个自定义组件内,包括其子组件中声明多个同名或者同别名的@Provide装饰的变量。
-
允许装饰的变量类型: Object、class、string、number、boolean、enum以及这些类型的数组。
-
@Provide必须指定变量初始值,@Consume禁止本地初始化。
示例代码如下:
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
@Provide装饰器和@Consume装饰器的具体使用方法与前几个装饰器非常类似。我们写一个Demo展示其用法。
在下面的示例是与后代组件双向同步状态@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()
}
}
}
界面如下,我们不管点击哪个按钮,所有的reviewVotes(0)中的数据都会同步更新。
3、one more thing
不知道有朋友注意到没有,目前我们学习到的状态管理装饰器有个概念:同步。那就意味着,数据变化后其他关联的状态也会发生变化。
默认情况下,数据的变化只能观测到一层,也就是,假设有个对象的属性也是一个对象,那嵌套的属性变化则不会触发状态同步。
class SimpleClass {
age: number;
name: string;
}
class ComplexClass {
simpleClass: SimpleClass;
}
如上代码,SimpleClass中的age和name变更后可以感知到变化,ComplexClass中simpleClass的age和name属性变化时,则无法感知,因为age和name是ComplexClass下面的SimpleClass的属性。
默认情况下,只能观测到一层属性的变化。
那我们想观测多层的变化,怎么办呢?
这就引出了@Observed/@ObjectLink装饰器。
4、@Observed/@ObjectLink装饰器。
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
-
被@Observed装饰的类,可以被观察到属性的变化;
-
子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
-
单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
限制条件
-
使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
-
@ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
示例:
class ClassA {
public c: number;
constructor(c: number) {
this.c = c;
}
}
@Observed
class ClassB {
public a: ClassA;
public b: number;
constructor(a: ClassA, b: number) {
this.a = a;
this.b = b;
}
}
以上示例中,ClassB被@Observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于ClassA,没有被@Observed装饰,其属性的修改不能被观察到。
@ObjectLink b: ClassB
// 赋值变化可以被观察到
this.b.a = new ClassA(5)
this.b.b = 5
// ClassA没有被@Observed装饰,其属性的变化观察不到
this.b.a.c = 5
@ObjectLink:@ObjectLink只能接收被@Observed装饰class的实例,可以观察到:
-
其属性的数值的变化,其中属性是指Object.keys(observedObject)返回的所有属性。
-
如果数据源是数组,则可以观察到数组item的替换,如果数据源是class,可观察到class的属性的变化。
5、结语
至此,我们已经了解完了所有组件状态管理相关的内容。接下来,我们再来学习应用级别的状态管理。
请持续关注“鸿蒙UI开发快速入门 —— part09”
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析