HarmonyOS NEXT应用开发之@Provide装饰器和\@Consume装饰器:与后代组件双向同步

本文主要是介绍HarmonyOS NEXT应用开发之@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类型。
API11及以上支持Map、Set类型。
支持类型的场景请参考 观察变化 。
API11及以上支持上述支持类型的联合类型,比如string | number, string | undefined 或者 ClassA | null,示例见 @Provide_and_Consume支持联合类型实例 。
注意
当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:@Provide a : string | undefined = undefined是推荐的,不推荐@Provide a: string = undefined

支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。
不支持any。
必须指定类型。
@Provide变量的@Consume变量的类型必须相同。
被装饰变量的初始值必须指定。
支持allowOverride参数允许重写,只要声明了allowOverride,则别名和属性名都可以被Override。示例见@Provide支持allowOverride参数。

@Consume变量装饰器说明
装饰器参数别名:常量字符串,可选。
如果提供了别名,则必须有@Provide的变量和其有相同的别名才可以匹配成功;否则,则需要变量名相同才能匹配成功。
同步类型双向:从@Provide变量(具体请参见@Provide)到所有@Consume变量,以及相反的方向。双向同步操作与@State和@Link的组合相同。
允许装饰的变量类型Object、class、string、number、boolean、enum类型,以及这些类型的数组。
支持Date类型。
支持类型的场景请参考 观察变化 。
API11及以上支持上述支持类型的联合类型,比如string | number, string | undefined 或者 ClassA | null,示例见@Provide_and_Consume支持联合类型实例。
注意
当使用undefined和null的时候,建议显式指定类型,遵循TypeScript类型校验,比如:@Consume a : string | undefined

支持ArkUI框架定义的联合类型Length、ResourceStr、ResourceColor类型。
不支持any。
必须指定类型。
@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初始化规则图示

image.png

@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()}}
}
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。

  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。

框架行为

  1. 初始渲染:

    1. @Provide装饰的变量会以map的形式,传递给当前@Provide所属组件的所有子组件;
    2. 子组件中如果使用@Consume变量,则会在map中查找是否有该变量名/alias(别名)对应的@Provide的变量,如果查找不到,框架会抛出JS ERROR;
    3. 在初始化@Consume变量时,和@State/@Link的流程类似,@Consume变量会保存在map中查找到的@Provide变量,并把自己注册给@Provide。
  2. 当@Provide装饰的数据变化时:

    1. 通过初始渲染的步骤可知,子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后,会遍历更新所有依赖它的系统组件(elementid)和状态变量(@Consume);
    2. 通知@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()}}
}

装饰Map类型变量

说明:

从API version 11开始,@Provide,@Consume支持Map类型。

在下面的示例中,message类型为Map<number, string>,点击Button改变message的值,视图会随之刷新。

@Component
struct Child {@Consume message: Map<number, string>build() {Column() {ForEach(Array.from(this.message.entries()), (item: [number, string]) => {Text(`${item[0]}`).fontSize(30)Text(`${item[1]}`).fontSize(30)Divider()})Button('Consume init map').onClick(() => {this.message = new Map([[0, "a"], [1, "b"], [3, "c"]])})Button('Consume set new one').onClick(() => {this.message.set(4, "d")})Button('Consume clear').onClick(() => {this.message.clear()})Button('Consume replace the first item').onClick(() => {this.message.set(0, "aa")})Button('Consume delete the first item').onClick(() => {this.message.delete(0)})}}
}@Entry
@Component
struct MapSample {@Provide message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])build() {Row() {Column() {Button('Provide init map').onClick(() => {this.message = new Map([[0, "a"], [1, "b"], [3, "c"], [4, "d"]])})Child()}.width('100%')}.height('100%')}
}

装饰Set类型变量

说明:

从API version 11开始,@Provide,@Consume支持Set类型。

在下面的示例中,message类型为Set<number>,点击Button改变message的值,视图会随之刷新。

@Component
struct Child {@Consume message: Set<number>build() {Column() {ForEach(Array.from(this.message.entries()), (item: [number, string]) => {Text(`${item[0]}`).fontSize(30)Divider()})Button('Consume init set').onClick(() => {this.message = new Set([0, 1, 2, 3, 4])})Button('Consume set new one').onClick(() => {this.message.add(5)})Button('Consume clear').onClick(() => {this.message.clear()})Button('Consume delete the first one').onClick(() => {this.message.delete(0)})}.width('100%')}
}@Entry
@Component
struct SetSample {@Provide message: Set<number> = new Set([0, 1, 2, 3, 4])build() {Row() {Column() {Button('Provide init set').onClick(() => {this.message = new Set([0, 1, 2, 3, 4, 5])})Child()}.width('100%')}.height('100%')}
}

Provide_and_Consume支持联合类型实例

@Provide和@Consume支持联合类型和undefined和null,在下面的示例中,count类型为string | undefined,点击父组件Parent中的Button改变count的属性或者类型,Child中也会对应刷新。

@Component
struct Child {// @Consume装饰的变量通过相同的属性名绑定其祖先组件Ancestors内的@Provide装饰的变量@Consume count: string | undefined;build() {Column() {Text(`count(${this.count})`)Button(`count(${this.count}), Child`).onClick(() => this.count = 'Ancestors')}.width('50%')}
}@Component
struct Parent {build() {Row({ space: 5 }) {Child()}}
}@Entry
@Component
struct Ancestors {// @Provide装饰的联合类型count由入口组件Ancestors提供其后代组件@Provide count: string | undefined = 'Child';build() {Column() {Button(`count(${this.count}), Child`).onClick(() => this.count = undefined)Parent()}}
}

@Provide支持allowOverride参数

allowOverride:@Provide重写选项。

说明:

从API version 11开始使用。

名称类型必填说明
allowOverridestring是否允许@Provide重写。允许在同一组件树下通过allowOverride重写同名的@Provide。如果开发者未写allowOverride,定义同名的@Provide,运行时会报错。
@Component
struct MyComponent {@Provide({allowOverride : "reviewVotes"}) reviewVotes: number = 10;
}

@Component
struct GrandSon {// @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量@Consume("reviewVotes") reviewVotes: number;build() {Column() {Text(`reviewVotes(${this.reviewVotes})`) // Text显示10Button(`reviewVotes(${this.reviewVotes}), give +1`).onClick(() => this.reviewVotes += 1)}.width('50%')}
}@Component
struct Child {@Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 10;build() {Row({ space: 5 }) {GrandSon()}}
}@Component
struct Parent {@Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 20;build() {Child()}
}@Entry
@Component
struct GrandParent {@Provide("reviewVotes") reviewVotes: number = 40;build() {Column() {Button(`reviewVotes(${this.reviewVotes}), give +1`).onClick(() => this.reviewVotes += 1)Parent()}}
}

在上面的示例中:

  • GrandParent声明了@Provide(“reviewVotes”) reviewVotes: number = 40
  • Parent是GrandParent的子组件,声明@Provide为allowOverride,重写父组件GrandParent的@Provide(“reviewVotes”) reviewVotes: number = 40。如果不设置allowOverride,则会抛出运行时报错,提示@Provide重复定义。Child同理。
  • GrandSon在初始化@Consume的时候,@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量。
  • GrandSon查找到相同属性名的@Provide在祖先Child中,所以@Consume(“reviewVotes”) reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide,则继续向上寻找Parent中的同名@Provide值为20,以此类推。
  • 如果查找到根节点还没有找到key对应的@Provide,则会报初始化@Consume找不到@Provide的报错。

常见问题

@BuilderParam尾随闭包情况下@Provide未定义错误

在此场景下,CustomWidget执行this.builder()创建子组件CustomWidgetChild时,this指向的是HomePage。因此找不到CustomWidget的@Provide变量,所以下面示例会报找不到@Provide错误,和@BuilderParam连用的时候要谨慎this的指向。

错误示例:

class Tmp {a: string = ''
}@Entry
@Component
struct HomePage {@Builderbuilder2($$: Tmp) {Text(`${$$.a}测试`)}build() {Column() {CustomWidget() {CustomWidgetChild({ builder: this.builder2 })}}}
}@Component
struct CustomWidget {@Provide('a') a: string = 'abc';@BuilderParambuilder: () => 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;@BuilderParambuilder: ($$: Tmp) => void;build() {Column() {this.builder({ a: this.a })}}
}

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

这篇关于HarmonyOS NEXT应用开发之@Provide装饰器和\@Consume装饰器:与后代组件双向同步的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/875788

相关文章

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

基于Python开发Windows自动更新控制工具

《基于Python开发Windows自动更新控制工具》在当今数字化时代,操作系统更新已成为计算机维护的重要组成部分,本文介绍一款基于Python和PyQt5的Windows自动更新控制工具,有需要的可... 目录设计原理与技术实现系统架构概述数学建模工具界面完整代码实现技术深度分析多层级控制理论服务层控制注

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

利用Python操作Word文档页码的实际应用

《利用Python操作Word文档页码的实际应用》在撰写长篇文档时,经常需要将文档分成多个节,每个节都需要单独的页码,下面:本文主要介绍利用Python操作Word文档页码的相关资料,文中通过代码... 目录需求:文档详情:要求:该程序的功能是:总结需求:一次性处理24个文档的页码。文档详情:1、每个

Java中的分布式系统开发基于 Zookeeper 与 Dubbo 的应用案例解析

《Java中的分布式系统开发基于Zookeeper与Dubbo的应用案例解析》本文将通过实际案例,带你走进基于Zookeeper与Dubbo的分布式系统开发,本文通过实例代码给大家介绍的非常详... 目录Java 中的分布式系统开发基于 Zookeeper 与 Dubbo 的应用案例一、分布式系统中的挑战二

Java 缓存框架 Caffeine 应用场景解析

《Java缓存框架Caffeine应用场景解析》文章介绍Caffeine作为高性能Java本地缓存框架,基于W-TinyLFU算法,支持异步加载、灵活过期策略、内存安全机制及统计监控,重点解析其... 目录一、Caffeine 简介1. 框架概述1.1 Caffeine的核心优势二、Caffeine 基础2

使用Node.js和PostgreSQL构建数据库应用

《使用Node.js和PostgreSQL构建数据库应用》PostgreSQL是一个功能强大的开源关系型数据库,而Node.js是构建高效网络应用的理想平台,结合这两个技术,我们可以创建出色的数据驱动... 目录初始化项目与安装依赖建立数据库连接执行CRUD操作查询数据插入数据更新数据删除数据完整示例与最佳

基于Go语言开发一个 IP 归属地查询接口工具

《基于Go语言开发一个IP归属地查询接口工具》在日常开发中,IP地址归属地查询是一个常见需求,本文将带大家使用Go语言快速开发一个IP归属地查询接口服务,有需要的小伙伴可以了解下... 目录功能目标技术栈项目结构核心代码(main.go)使用方法扩展功能总结在日常开发中,IP 地址归属地查询是一个常见需求:

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Vue和React受控组件的区别小结

《Vue和React受控组件的区别小结》本文主要介绍了Vue和React受控组件的区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录背景React 的实现vue3 的实现写法一:直接修改事件参数写法二:通过ref引用 DOMVu