前言
ETS(基于 TS 扩展的声明式开发范式)提供了强大的组件化能力,支持开发者高效的构建应用的 UI 界面,然而每一个组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互进行直接的引用,因此组件间的相互通信是非常重要的。
组件间通信原理
ETS 组件间通信是通过组合不同的装饰器来实现数据在各个组件之间的单向/双向传递。
相关装饰器介绍
装饰器名称 | 功能 | 特征 |
---|---|---|
@State | 所装饰变量的值发生变化时,将会调用所在组件的 build 方法进行 UI 刷新 | 支持装饰多种数据类型的变量(不允许 object 和 any);允许组件内存在多个被其装饰的变量;内部私有(@State 装饰的的变量是私有变量,只能在组件内访问);需要本地初始化(必须为所有 @State 装饰的变量分配初始值,否则可能导致框架行为未定义);创建自定义组件时支持通过状态变量名设置初始值 |
@Prop | 具备 @State 的功能,装饰的变量能够和父组件中被 @State 装饰的变量建立单向数据绑定(只支持父组件传递数据至子组件),用于父组件向子组件传递数据 | 仅支持装饰简单类型(boolean,number,string)的变量;仅允许在组件内访问;允许组件内存在多个被其装饰的变量;装饰的所有变量必须使用其父组件提供的被 @State 装饰的变量进行初始化,不支持在组件内部进行初始化 |
@Link | 具备 @State 的功能,装饰的变量可以和父组件中被 @State 装饰的变量建立双向数据绑定,用于父子组件间数据的双向传递 | 支持装饰多种数据类型的变量;仅在组件内访问; 双向通信(子组件对 @Link 装饰的变量的更改将同步修改父组件中被 @State 装饰的变量), 装饰的所有变量必须使用其父组件提供的被 @State 或 @Link 装饰的变量进行初始化,不支持在组件内部进行初始化;进行数据传递时需要通过’$’操作符将父组件的数据传给子组件 |
@Provide | 具备 @State 的功能,可以和子/孙组件中被 @Consume 装饰的变量建立双向数据绑定 | 支持装饰多种数据类型的变量;该装饰器可以指定参数,用于给装饰的变量起别名,推荐使用 @Provide(“alias”)这种形式;需要指定初始值;一般和 @Consume 搭配使用;传递数据的方式比 @Link/@Prop 更简单,只要 @Consume 的参数值和 @Provide 的参数值一致就可以进行数据的双向传递,若两者都没有参数值,则它们装饰的变量名必须相同;@Provide/@Consume 可以代替 @State/@Link 传递数据,但是更适用于组件数据的跨层级传递的场景(例如组件 1 嵌套了组件 2,组件 2 嵌套组件 3,组件 3 嵌套了组件 4,要实现组件 1 和组件 4 之间的数据传递,使用 @Provide/@Consume 进行数据传递会更加方便) |
@Consume | 具备 @State 的功能,与 @Provide 搭配使用 | 不可设置默认值,必须和 @Provide 一起使用;使用 @Provide 和 @Consume 时避免循环引用导致死循环;使用了 @Consume 装饰器的组件上不能有 @Entry 装饰,否则程序会报错 |
@StorageLink | 具备 @State 的功能,能与 AppStorage 建立双向数据绑定(AppStorage 是应用程序中的单例对象,与应用的生命周期相同,为应用程序范围内的可变状态属性提供中央存储) | 支持装饰多种数据类型的变量; 被其装饰的变量即可以在组件内部初始化,也可以使用 AppStorage 中的值初始化;与 AppStorage 建立双向数据绑定,在 UI 组件中对 @StorageLink 装饰的变量所做的更改将同步到 AppStorage,并从 AppStorage 同步到任何其他绑定实例中;应用范围内的任意两个或多个组件都可以借助 AppStorage 进行数据传递 |
@Watch | 用于监听被指定装饰器所装饰变量的变化 | 需要指定参数值,参数值是方法的名称;装饰的变量上必须有其他装饰器装饰;可以监听装饰器 @State、@Prop、@Link、@ObjectLink、@Provide、@Consume、@StorageLink 装饰的变量的变化(注意:监听 @StorageLink 装饰的变量时,@StorageLink 的参数值必须和其装饰的变量名一致) |
组件间通信实现
父子组件通信
数据单向传递
使用 @State 和 @Prop 装饰器可以实现父组件向子组件单向传递数据。
定义子组件 Child
@Component
struct Child {
@Watch("listenChildCount") //当count变量值发生变化,会调用listenChildCount()
@Prop
count: number;
build() {
Column() {
Text("子组件:" + this.count).fontSize(30)
Button("reduce")
.fontSize(25)
.onClick(() => {
this.count--;
})
}
.border({ width: 2, color: Color.Blue })
.backgroundColor(Color.Green)
.width("50%")
.height("50%")
}
/**
* 用于监听count变量的变化
*/
listenChildCount() {
console.info("ChildComponent count changed:" + this.count)
}
}
定义父组件,在父组件中嵌套子组件 Child,并将父组件中的 count 变量传递给子组件
@Entry
@Component
struct Parent {
@State
@Watch("listenCount") //当count变量值发生变化,会调用listenCount()
count: number = 0;
build() {
Column() {
Column() {
Text("父组件:" + this.count)
.fontSize(30)
.fontColor(Color.Red)
Button("add")
.fontColor(Color.White)
.fontSize(30)
.backgroundColor(Color.Orange)
.onClick(() => {
this.count++;
})
Child({ count: this.count })
}.border({ width: 2, color: Color.Black })
.width("100%")
.height(400)
.backgroundColor(Color.Gray)
}
}
listenCount() {
console.info("ParentComponent count changed:" + this.count)
}
}
效果如下:
数据双向传递
@State/@Link 实现父子组件数据双向传递
将上文的子组件中 count 变量上的装饰器改为 @Link
@Component
struct Child {
@Link
@Watch("listenCount")
count: number;
build() {
...
...
}
...
}
改变上文中父组件传递数据到子组件的方式
@Entry
@Component
struct Parent {
...
build() {
Column() {
...
...
Child({ count: $count }) //@Link需要用'$'符号传递变量
}.border({ width: 2, color: Color.Black })
.width("100%")
.height(400)
.backgroundColor(Color.Gray)
}
...
}
效果如下:
@Provide/@Consume 实现父子组件数据双向传递
将上文父组件中 count 变量上的装饰器替换为 @Provide,子组件中 count 变量上的装饰器替换成 @Consume:
@Entry
@Component
struct Parent {
//这里@Provide必须放在@Watch的上面,否则会报错
@Provide("Count")
@Watch("listenCount") //当count变量值发生变化,会调用listenCount()
count: number = 0;
build() {
Column() {
Column() {
Text("父组件:" + this.count)
.fontSize(30)
.fontColor(Color.Red)
Button("add")
.fontColor(Color.White)
.fontSize(30)
.backgroundColor(Color.Orange)
.onClick(() => {
this.count++;
})
Child() //使用@Provide 、@Consume不需要像@Link、@Prop那样传数据
}.border({ width: 2, color: Color.Black })
.width("100%")
.height(400)
.backgroundColor(Color.Gray)
}
}
listenCount() {
console.info("ParentComponent count changed:" + this.count)
}
}
@Component
struct Child {
//这里@Consume必须放在@Watch的上面,否则会报错
@Consume("Count") //@Consume的参数值必须和对应的@Provide的参数值一致
@Watch("listenChildCount") //当count变量值发生变化,会调用listenChildCount()
count: number;
build() {
Column() {
Text("子组件:" + this.count)
.fontSize(30)
Button("reduce")
.fontSize(25)
.onClick(() => {
this.count--;
})
}
.border({ width: 2, color: Color.Blue })
.backgroundColor(Color.Green)
.width("50%")
.height("50%")
}
listenChildCount() {
console.info("ChildComponent count changed:" + this.count)
}
}
效果:效果与上一步相同
@Provide/@Consume 实现组件跨层级双向传递数据
定义子组件 GrandChild
@Component
struct GrandChild {
@Consume
count: number;
build() {
Column() {
Text("GrandChild:" + this.count)
.fontSize(30)
Button("reduce")
.fontSize(25)
.onClick(() => {
this.count--;
})
}
.border({ width: 2, color: Color.Blue })
.backgroundColor(Color.Gray)
.width(200)
.height(180)
}
}
定义子组件 Child,在 Child 组件中嵌套 GrandChild 组件
@Component
struct Child {
build() {
Column() {
Text("Child")
.fontSize(40)
.margin({ top: 25 })
GrandChild()
}
.border({ width: 2, color: Color.Blue })
.backgroundColor(Color.Green)
.width(300)
.height(300)
}
}
定义父组件 Parent,在 Parent 组件中嵌套 Child 组件
@Entry
@Component
struct Parent {
@Provide("Count")
count: number = 0;
build() {
Column() {
Column() {
Text("Parent:" + this.count)
.fontSize(30)
.fontColor(Color.Red)
Button("add")
.fontColor(Color.White)
.fontSize(30)
.backgroundColor(Color.Orange)
.onClick(() => {
this.count++;
})
Child()
}.border({ width: 2, color: Color.Black })
.width("100%")
.height(400)
.backgroundColor(Color.Gray)
}
}
}
效果如下:
兄弟组件通信
ETS 中要实现兄弟组件间传递数据需要借助第三个组件,即两个需要传递数据的组件需要有共同的父组件
@Provide/@Consume 实现兄弟组件双向传递数据
定义子组件 Child1、Child2,并用 @Consume 装饰对应变量
@Component
struct Child1 {
@Consume("Count")
count1: number
build() {
Column() {
Text("Child1:" + this.count1)
.fontSize(40)
Button("add")
.fontColor(Color.White)
.fontSize(30)
.backgroundColor(Color.Orange)
.onClick(() => {
this.count1++;
})
}
.border({ width: 2, color: Color.Blue })
.backgroundColor(Color.Green)
.width("100%")
.height(180)
}
}
@Component
struct Child2 {
@Consume("Count")
count2: number;
build() {
Column() {
Text("Child2:" + this.count2)
.fontSize(30)
Button("reduce")
.fontSize(25)
.onClick(() => {
this.count2--;
})
}
.border({ width: 2, color: Color.Blue })
.backgroundColor(Color.Yellow)
.width("100%")
.height(180)
}
}
定义父组件 Parent,嵌套组件 Child1、Child2
@Entry
@Component
struct Parent {
@Provide("Count")
count: number = 0;
build() {
Column() {
Child1()
Child2()
}
.width("100%")
.height("100%")
}
}
效果如下:
@State 和 @Link 实现兄弟组件双向传递数据
将子组件 Child1 和 Child2 中 count 变量上的装饰器改为 @Link
@Component
struct Child1 {
@Link
count1: number
build() {
...
..
}
}
@Component
struct Child2 {
@Link
count2: number;
build() {
...
...
}
}
将上一步父组件 Parent 中 count 变量上的装饰器改为 @State
@Entry
@Component
struct Parent {
@State
count: number = 0;
build() {
...
Child1({ count1: $count })
Child2({ count2: $count })
...
}
}
效果:与上一步的效果相同
任意组件通信
借助 AppStorage 和 @StorageLink 可以实现任意两个组件(包括同一个页面里的两个组件、不同页面里的组件、不同 Ability 里的两个组件)
AppStorage/@StorageLink 实现不同页面组件间传递数据
定义页面 Page1
import router from '@ohos.router'; //router用于进行页面跳转
AppStorage.SetOrCreate("Count", 0); //先向AppStorage中存一个key:value
@Entry
@Component
struct Component1 {
@StorageLink("Count")
count: number = AppStorage.Link("Count") //与AppStorage进行双向绑定,用AppStorage里的值初始化count
build() {
Column() {
Text("页面1:" + this.count)
.fontSize(50)
Button("add")
.margin({ bottom: 25 })
.onClick(() => {
this.count++;
})
Button("跳转")
.fontSize(25)
.onClick(() => {
router.push({ url: "pages/page2" })
})
}.border({ width: 2, color: Color.Blue })
.width("100%")
.height(400)
.backgroundColor(Color.Gray)
}
}
定义页面 Page2
import router from '@ohos.router';
@Entry
@Component
struct Component2 {
@StorageLink("Count")
count: number = AppStorage.Link("Count") //与AppStorage进行双向绑定,用AppStorage里的值初始化count
build() {
Column() {
Text("页面2:" + this.count)
.fontSize(50)
.fontColor(Color.Green)
Button("reduce")
.margin({ bottom: 25 })
.onClick(() => {
this.count--;
})
Button("跳转")
.fontSize(25)
.onClick(() => {
router.back({ url: "pages/page1" })
})
}.border({ width: 2, color: Color.Blue })
.width("100%")
.height(400)
.backgroundColor(Color.Orange)
}
}
效果如下:
最后
小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,不同的角度的问了一些问题,我明显感觉到一点,那就是许多人参与鸿蒙开发,但是又不知道从哪里下手,因为资料太多,太杂,教授的人也多,无从选择。有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
为了确保高效学习,建议规划清晰的学习路线,涵盖以下关键阶段:
→【纯血版鸿蒙全套最新学习资料】希望这一份鸿蒙学习资料能够给大家带来帮助~
鸿蒙(HarmonyOS NEXT)最新学习路线
该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案
路线图适合人群:
IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术
2.视频学习资料+学习PDF文档
HarmonyOS Next 最新全套视频教程 (鸿蒙语法ArkTS、TypeScript、ArkUI教程……)
纯血版鸿蒙全套学习资料(面试、文档、全套视频等)
鸿蒙APP开发必备
总结
【纯血版鸿蒙全套最新学习资料】
总的来说,华为鸿蒙不再兼容安卓,对程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,才能在这个变革的时代中立于不败之地。