组件间的通信主要作用是能在组件间传递数据或者执行相关的业务逻辑,对于鸿蒙应用组件,下面将对几种组件间的通信方式进行代码实现,其中包括实现自定义实现任意组件通信。
文件结构
首先我们准备好几个组件 parent组件、current组件、child1组件、child2组件,其中parent与current是父子组件关系、curren与child1/child2是父子组件关系、child1与child2是兄弟组件关系、parent与child1/child2是跨层级组件的关系,详细的代码文件可以参考附件源码,最终预览得到结果如下:
父子关系通信
parent与current是父子组件关系,在current.js中props的title=''current-title",在parent.hml中使用current组件时传入title="parent"时,current组件显示parent,这里使用了props由父组件向子组件传递title值进行通信,预览结果见下图:
curren与child1/child2是父子组件关系,在current组件中有三个按钮 改变parent、改变child1、改变child2,按钮配置如下:
在curren组件中,点击改变parent按钮会触发changeParent方法,点击改变child1按钮会触发changeChild1方法,点击改变child1按钮会触发changeChild1方法,这些方法会改变对应组件中的数据,如下:
点击改变parent按钮,通过使用$parent方法得到parent组件的实例,然后通过实例对象与parent组件进行数据通信:
点击改变child1,通过使用$child方法得到child1组件的实例,然后通过实例对象与child1组件进行数据通信:
点击改变child2,通过使用$child方法得到child2组件的实例,然后通过实例对象与child2组件进行数据通信:
在changeParent方法中使用this.$parent()方法来获取了parent组件的ViewModel,从而可以访问和改变parent组件的数据
在changeChild1和changeChild2中使用了this.$child(id)方法来获取child1/child2组件的ViewModel,从而可以访问和改变child1/child2组件的数据
除了使用this.$parent方法外还可以使用自定义事件的方式来进行子组件调用父组件方法来向父组件传值,parent组件在引用current组件时绑定事件,在current组件中调用this.$emit方法来触发绑定的自定义事件。
parent.hml中绑定自定义的eventEmit方法
current.js中使用this.$emit方法触发eventEmit方法:
总结下来,父子组件间的通信方式大概有几种方法:
- props:用于父组件向子组件传递数据
- $emit:用于自组件向父组件传递数据或调用父组件方法
- $parent:用于获取父组件ViewModel从而可以修改父组件数据和调用父组件方法
- $child:用于获取子组件ViewModel从而修改子组件数据和调用子组件方法
兄弟关系与跨层级组件间通信
根据上面父子组件的通信方式,对于兄弟组件和跨层级组件之间,可以组合使用上面的几种方法组合的方式可以实现非父子组件间的通信,但是如果组件间嵌套比较复杂,嵌套层级比较多的情况下,使用以上方法组合的方式显然不太方便,在这里尝试实现一种类似vue bus全局通信的方式来进行任意组件间的通信。
在JS API中app.js文件中定义的数据可以通过this.app.app.def获取,根据这个特性和使用观察者模式来设计一可以全局订阅响应的通信模式。
eventBus
首先我们定义evnetBus.js文件,在里面定义相关的订阅、发布和取消订阅的方法:
- const Bus = {
- // 发布的事件集合
- $events:{},
- /**
- * 发布事件方法
- * type: string 字符串
- * fun: function 绑定的方法
- * */
- $on(type,fun){
- },
- /**
- * 触发事件方法
- * type: string 发布事件的字符串
- * args: 传参
- * */
- $emit(type,...args){
- },
- /**
- * 注销事件方法
- * type: string 字符串
- * fun: string|function 发布事件时返回的值或者发布的原function
- * */
- $off(type,fun){
- }
- }
- export default Bus;
在里面我们定义了$events对象用于绑定事件的存放,$on方法用于绑定事件,$emit方法用于触发事件,$off方法用于注销事件,完整的eventBus.js如下:
eventBus.js
- const Bus = {
- // 发布的事件集合
- $events:{ },
- /**
- * 发布事件方法
- * type: string 字符串
- * fun: function 绑定的方法
- * */
- $on(type,fun){
- let res = "";
- if(type && typeof type == "string" && fun && typeof fun =="function"){
- let events = this.$events[type];
- if(events){
- let index = events.findIndex(null);
- if(index > -1){
- res = `${String(index)}${type}`;
- events[index] = fun;
- }else{
- events.push(fun);
- }
- }else{
- this.$events[type] = [fun];
- res = `0${type}`;
- }
- }
- return res;
- },
- /**
- * 触发事件方法
- * type: string 发布事件的字符串
- * args: 传参
- * */
- $emit(type,...args){
- if(type && typeof type == "string"){
- let events = this.$events[type];
- if(events){
- events.forEach(fun => {
- if(fun && typeof fun =="function"){
- fun(...args);
- }
- });
- }
- }
- },
- /**
- * 注销事件方法
- * type: string 字符串
- * fun: string|function 发布事件时返回的值或者发布的原function
- * */
- $off(type,fun){
- if(type && typeof type == "string" && fun){
- let events = this.$events[type];
- if(events){
- if(typeof fun == "string"){
- let indexStr = fun.replace(type,'');
- let index = parseInt(indexStr);
- events[index] = null;
- }
- if(typeof fun == "function"){
- events.forEach(item => {
- if(item == fun){
- item = null;
- }
- });
- }
- }
- }
- }
- }
- export default Bus;
使用方法
引入
首先在app.js中引入eventBus,将eventBus绑定在我们的全局对象上:
绑定
在parent组件的onInit方法中绑定事件,可以通过绑定的事件来改变parent组件的值,使用this.$app.$def方法获取全局app下挂载的eventBus对象:
触发
然后我们修改child1组件,添加按钮使用eventBus对象的$emit方法来触发bus事件:
在触发方法的时候我们传入了自定义参数数据"123456789"
在点击按钮后将会触发我们在parent组件中绑定的方法从而改变parent组件数据,同时也接收到了child组件传过来的数据"123456789"
在此就实现了跨层级组件间的通信,此方法可以对任意组件间的事件触发和传值都有效,限于篇幅不再展示,最后我们还要在组件销毁的时候注销我们绑定的事件。
注销
我们定义一个注销事件的按钮并绑定注销事件:
在我们$on绑定事件的时候可以得到一个返回值,这个值也可以用于事件的注销,在这里就不做演示了,有兴趣的可以下去尝试一下。
总结
以上就是使用观察者模式实现了一个简易版的事件总线机制可以在任意组件间通信,基本功能可以实现,还有一些缺点需要提示:
首先这个在使用的时候必须依赖鸿蒙JS API this.$app.$def 这个方法,每次使用都要用这个来获取eventBus对象。
在注册绑定事件后一定要记得在当前组件销毁的时候注销绑定的事件,否则可能会造成内存泄露。
事件注销有两种方式,需要稍微理解注销事件的原理才能熟练使用。