依赖注入+中央事件总线:Vue 3组件通信新玩法

​🌈个人主页:前端青山
🔥系列专栏:Vue篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来Vue篇专栏内容:Vue-依赖注入-中央事件总线

目录

中央事件总线使用

依赖注入使用

总结

中央事件总线

依赖注入

结言

大家好,依旧青山,

最近呢也随着需求的变更调优,加载数字孪生地图的缓慢,要将原有vue3+Ts数据大屏子菜单整合到一个地图环境下(注:无需加载其余地图场景,同一地图环境下切换不同菜单),也就是主页面及子菜单调用一次地图环境即可,页面很好集合前嵌套,但是不同页面对地图的操作该如何呢?

那么我首先做的就是封装一个公共的地图调用方法,以组件形式引入所有子菜单实现跨组件通信!

以数字孪生地图为例

import { ElLoading } from 'element-plus'
import mapJson from '@/utils/tjbhJson';
import textArr from '@/utils/textJson';
import cloudRenderer from "51superapi"
import { ref, onMounted, onBeforeUnmount,reactive,watchEffect } from "vue"
// 引入前缀路径
//封装51地图函数
const app = new cloudRenderer("mapDiv");
export default function (){let loadingInstance: any; // 在更宽泛的作用域定
const prefixUrl = import.meta.env.VITE_APP_BASE_API || '';
//配置51地图参数
const startRenderConfig = reactive({"url": "http://192.168.1.20:8080", //[必须] 云渲染服务地址; 8889:固定端口"order": "123456", //[必须] 渲染口令; 在云渲染客户端上获得"resolution": [window.innerWidth > 1920? 4096 : window.innerWidth,window.innerHeight >1080? 1209 : window.innerHeight], //[可选] 设置渲染场景像素分辨率"nodestyle": `width:${window.innerWidth > 1920? 4096 : window.innerWidth};height:${window.innerHeight >1080? 1209 : window.innerHeight};position:absolute;top:0px;left:0px;bottom:0px;right:0px;margin:auto;`, //[可选] 设置渲染场景容器DOM节点样式, 与设置渲染场景像素分辨率配对使用"keyboard": "keyboardnofn", //[可选] 初始建盘事件, 开启wasd方向键 [选项: keyboard/keyboardnofn; 详见注册键盘事件]"setlogmode": true, //[可选] 开启/关闭SuperAPI调用日志, 默认false
})
// 设置初始分辨率
startRenderConfig.resolution = [window.innerWidth > 1920 ? 4096 : window.innerWidth,window.innerHeight > 1080 ? 1209 : window.innerHeight,
];
//围绕中心旋转
let jsonData = {"time": 50,                      //相机旋转一周所需要的时间, (单位:秒)"direction": "stop"         //clockwise:顺时针; anticlockwise:逆时针; stop:停止旋转
}
//添加区域轮廓
let jsondata2 = {"id": "range_id","coord_type": 0,                  //坐标类型(0:经纬度坐标, 1:cad坐标)"cad_mapkey": "",                 //CAD基准点Key值, 项目中约定"coord_z": 0,                     //高度(单位:米)"coord_z_type": 0,                //坐标高度类型(0:相对3D世界表面;1:相对3D世界地面;2:相对3D世界海拔; 注:cad坐标无效)"type": "loop_line",                   //样式类型; 注①"color": "ffffff",              //轮廓颜色(HEXA颜色值)"range_height": 60,               //围栏高度(单位:米)"stroke_weight": 10,              //底部轮廓线宽度(单位:米; 注: 区域中含有内环"inner_points"时无效)"fill_area": "none",              //底部区域填充类型; 注②"geojson": mapJson,                 //geojson数据; 注③
​
}
//添加3d文字信息与区域轮廓
const pushAllCovering = () => {app.SuperAPI("Add3DText", textArr, (status: any) => {console.log(status); //成功、失败回调});//添加区域轮廓app.SuperAPI('AddGeoRange', jsondata2).then((_back: any) => {})
}
//初始地图视角
const Camejsondata = {"coord_type": 0,                                 //坐标类型(0:经纬度坐标, 1:cad坐标)"cad_mapkey": "",                                //CAD基准点Key值, 项目中约定"coord_z": "2.06",                               //海拔高度(单位:米)"center_coord": "117.689178,39.01527",           //中心点的坐标 lng,lat"arm_distance": 3000,                            //镜头距中心点距离(单位:米)"pitch": 30,                                     //镜头俯仰角(5~89)"yaw": 70,                                        //镜头偏航角(0正北, 0~359)"fly": true                                      //true: 飞行动画(有一个短暂飞行动画,并按照arm_distance,pitch,yaw设置镜头);//false: 立刻跳转过去(瞬移)
}
//设置渲染质量
let jsonDate ={"quality": "epic"   //low:低; medium:中; high:高; epic:超高;
}
// 地图事件注册函数
const myHandleResponseFunction = (data: string) => {const jsonObject = typeof data === "object" ? JSON.parse(JSON.stringify(data)) : JSON.parse(data);switch (jsonObject.func_name) {case "APIAlready":app.SuperAPI("RemoveAllCovering", {covering_type: "all", //覆盖物类型, 详见下表}).then((_back: any) => {console.log(_back);});pushAllCovering(); //添加区域轮廓//设置镜头绕场景中心点旋转app.SuperAPI("SetCameraRotate", jsonData, (e: any) => {
​})//设置当前场景镜头视界app.SuperAPI("SetCameraInfo", Camejsondata, (status: any) => {})app.SuperAPI("SetRenderQuality", jsonDate, (status:any) => {console.log(status,'设置渲染质量'); //成功、失败回调})loadingInstance.close();break;case 'OnPOIClick':const coord = jsonObject.args.coord;const poiId = jsonObject.args.id;console.log(poiId,"poiId")break;}return data;
}
const myStartRender = async () => {try {// 设置初始分辨率
startRenderConfig.resolution = [window.innerWidth > 1920 ? 4096 : window.innerWidth,window.innerHeight > 1080 ? 1209 : window.innerHeight,
];await app.startRender(startRenderConfig).then((el: any) => {loadingInstance = ElLoading.service({ // 赋值给外部变量lock: true,text: '地图加载中',background: 'rgba(0, 0, 0, 0.7)',});// 事件注册;事件监听处理器函数, 接收所有从云渲染返回的事件, 数据等信息app.RegisterCloudResponse(myHandleResponseFunction);})} catch (error) {console.error("error:", error)}
}
const SuperAPI = () => {//先删除全部覆盖物                      覆盖物类型, 详见下表app.SuperAPI("RemoveAllCovering", { "covering_type": "poi" }, (status: any) => {console.log(status); //成功、失败回调})
}
// 监听窗口大小变化
watchEffect(() => {startRenderConfig.resolution = [window.innerWidth > 1920 ? 4096 : window.innerWidth,window.innerHeight > 1080 ? 1209 : window.innerHeight,];
});
return { app, startRenderConfig, myStartRender, myHandleResponseFunction,prefixUrl,loadingInstance, SuperAPI }
}

把公共地图渲染部分封装为一个ts文件,并暴露出myStartRender函数方便在主页面onMounted函数中调用并渲染地图,依次执行即可,那么大家可以看到还暴露出一个app进行全局调用,是因为这个数字孪生地图的操作都要以app.(地图操作Api)的形式调用

最终我们在页面中删除公共部分,只需引入公共函数即可!

<template><div id="main-content"><!-- 地图盒子 --><div id="mapDiv"></div><Header :naturalHazards="header"/><div v-if="header == '主页面'"><NaturalHazard/></div><div v-else-if="header == '菜单一'"><EarlyWarningDetection /></div><div v-else-if="header == '菜单二'"><DisasterGeneralData /></div><div v-else-if="header == '菜单三'"><JobFacilities /></div><div v-else-if="header == '菜单四'"><RiskHiddenDanger /></div><div v-else-if="header == '菜单五'"><VideoSurveillance /></div><div v-else-if="header == '菜单六'"><HydrologicMonitoring /></div><div v-else-if="header == '菜单七'"><FloodFightingMaterials /></div><div v-else-if="header == '菜单八'"><RescueTeam /></div></div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, nextTick, provide } from "vue";
//引入51地图SuperAPI
import useSuperApi from "@/utils/useSuperApi"
const {app, prefixUrl,SuperAPI,
} = useSuperApi()
onMounted(() => {nextTick(() => {myStartRender()})
});
onBeforeUnmount(() => {app.StopRenderCloud(); //关闭云渲染, 释放资源
​
})
</script>

那么随之而来问题也就来了,当地图出现poi点的时候,我们点击对应的poi点肯定要实现不同的事件,我们现在所封装的app暴露出来可以进行打点操作,点击poi点的操作是由地图函数内部执行

// 地图事件注册函数
const myHandleResponseFunction = (data: string) => {const jsonObject = typeof data === "object" ? JSON.parse(JSON.stringify(data)) : JSON.parse(data);switch (jsonObject.func_name) {case 'OnPOIClick':const coord = jsonObject.args.coord;const poiId = jsonObject.args.id;console.log(poiId,"poiId"点击poi点所获得的id及经纬度)break;}return data;
}

在没有整合之前调用的时候是在当前页面的地图函数下执行,请看下方

// 地图事件注册函数
const myHandleResponseFunction = (data: string) => {const jsonObject = typeof data === "object" ? JSON.parse(JSON.stringify(data)) : JSON.parse(data);switch (jsonObject.func_name) {case 'OnPOIClick':const coord = jsonObject.args.coord;const poiId = jsonObject.args.id;handlePOIClick(poiId, coord);break;}return data;
​
}
// 处理自定义POI Label点击事件的函数
const handlePOIClick = (poiId: string, coord: string) => {const [type, id] = poiId.split('_'); // 分割前缀和IDswitch (type) { // 假设id格式为"type_ID",通过前缀区分类型case 'ggwhcs'://公共文化场所handelGgwhcs(id);break;case 'lyjq'://旅游景区handelLyjq(id);break;default:console.log(`未识别的POI类型: ${poiId}`);break;}
}

那么现在我们封装成一个公共函数,且渲染地图只在主页面调用,就要想办法将函数内部的poiId和coord作为参数暴露出去,方便我们每个子页面调用执行不同的操作,这里我就想到了vue的中央事件总线和依赖注入!

Vue3提供了多种机制来支持组件间的通信,包括中央事件总线和依赖注入。选择哪种方式取决于具体的应用场景和需求

中央事件总线使用

在处理地图的poi点点击事件时,我们可以先使用中央事件总线来执行我们组件不同页面点击poi点的处理逻辑,

首先,在utils文件夹下创建一个EventBus.ts文件

//封装中央事件总线
class EventBus {private events: Record<string, Function[]> = {};
​on(event: string, callback: Function) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(callback);}
​off(event: string, callback: Function) {if (!this.events[event]) return;this.events[event] = this.events[event].filter(cb => cb !== callback);}
​emit(event: string, ...args: any[]) {if (!this.events[event]) return;this.events[event].forEach(callback => callback(...args));}
}
​
const eventBus = new EventBus();
export default eventBus;

main.ts中创建一个全局的事件总线

app.config.globalProperties.$bus = {}; // 直接在全局属性中创建事件总线

然后再封装的内部地图函数poi点击事件时进行发送事件

case 'OnPOIClick':const coord = jsonObject.args.coord;const poiId = jsonObject.args.id;console.log(poiId,"poiId")eventBus.emit('poi-click', poiId, coord); // 发送事件break;

然后再主页面和各个子页面引入eventBus,在onMountedonBeforeUnmount监听和移出事件总线

onMounted(() => {nextTick(() => {eventBus.on('poi-click', handlePOIClick);//handlePOIClick为poi点击事件init();//初始化函数})
});
onBeforeUnmount(() => {//移出监听eventBus.off('poi-click', handlePOIClick);
})

这时,不管是我们的主页面,还是子菜单,都可以在切换的时候对应页面的poi点进行不同的处理逻辑了

依赖注入使用

单个菜单调用地图不同服务的事情解决了,那子菜单和主页面或子菜单和子菜单之间还有通信的复杂操作呢

比如在主页面的Echarts图表中,柱状图列出了A页面和B页面的统计数据,当我点击不同的柱状图时要切换到当前菜单,并直接选中状态及地图出现对应的操作,这时,基于这种复杂的操作我们可以使用依赖注入

在主页面先通过ref绑定对应组件,并引入provide提供依赖

<template><div id="main-content"><!-- 地图盒子 --><div id="mapDiv"></div><Header :naturalHazards="header"/><div v-if="header == '主页面'"><NaturalHazard/></div><div v-else-if="header == '菜单一'"><EarlyWarningDetection /></div><div v-else-if="header == '菜单二'" ref="disasterGeneralData"><DisasterGeneralData /></div><div v-else-if="header == '菜单三'"><JobFacilities /></div><div v-else-if="header == '菜单四'"><RiskHiddenDanger /></div><div v-else-if="header == '菜单五'"><VideoSurveillance /></div><div v-else-if="header == '菜单六'"><HydrologicMonitoring /></div><div v-else-if="header == '菜单七'"><FloodFightingMaterials /></div><div v-else-if="header == '菜单八'"><RescueTeam /></div></div>
</template>
​
<script setup lang="ts">
import { Search } from '@element-plus/icons-vue'
import { ref, onMounted, onBeforeUnmount, nextTick, provide } from "vue";
eam/index.vue";
import useSuperApi from "@/utils/useSuperApi";
import eventBus from '@/utils/EventBus';
const {app, myStartRender,prefixUrl,SuperAPI
} = useSuperApi()
const header = ref<any>("自然灾害")
let disasterGeneralData = ref<any>()
const setNames = (name: any) => {//这里我们提供一个函数来接收传进来的name
}
provide("setNames",setNames)
onMounted(() => {nextTick(() => {myStartRender()})
});
onBeforeUnmount(() => {app.StopRenderCloud(); //关闭云渲染, 释放资源
​
})
</script>
​
<style scoped lang="scss">
​
</style>

然后在我们的图表组件页面中注入依赖

let setNames: any = inject("setNames")

当我们点击对应的echarts图表时

      zgrwczqktance.value.on('click', (params: any) => {nextTick(() => {setNames(params.name)//传入对应name})});

那么我们子菜单页面肯定是要通过传入的name来执行不同的地图操作或展示详情等逻辑...

const setName = (name: any) => {const mappings:any = {"菜单一": [1, '菜单一'],"菜单二": [3, '菜单二'],"菜单三": [6, '菜单三'],"菜单四": [11, '菜单四'],"菜单五": [7, '菜单五'],};const [id, description] = mappings[name] || [];if (id !== undefined) {(name === "菜单一" || name === "菜单二" || name === "菜单四" || name === "菜单五" || name === "菜单三")? abreastClicks(id, description): abreastClick(id, description);}
};

然后我们把这个方法通过defineExpose给暴露出去

defineExpose({setName
})

最后在我们的主页面通过ref所绑定实例再取到依赖注入传入的参数和暴露的内部方法来进行通信啦

let disasterGeneralData = ref<any>()//ref绑定实例
const setNames = (name: any) => {disasterGeneralData.value.setName(name)//子菜单内部的setName方法(已暴露)
}

总结

中央事件总线
  • 优点

    • 简单易用,适用于较小规模的应用程序。

    • 不需要修改现有组件即可添加新的监听器。

  • 缺点

    • 随着应用规模的增长,事件名称可能会变得难以管理和追踪。

    • 可能导致组件间的耦合度增加。

依赖注入
  • 优点

    • 更好的组织性和可维护性,因为依赖关系是显式的。

    • 适用于需要在多个组件间共享数据和服务的情况。

    • 支持树状结构中的组件通信,无需直接父子关系。

  • 缺点

    • 对于简单的通信场景可能显得过于复杂。

    • 如果过度使用,可能会导致组件之间过于紧密的耦合。

结言
  • 对于简单的跨组件通信,可以考虑使用中央事件总线。

  • 对于更复杂的通信需求,依赖注入提供了更好的组织性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/403349.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【TiDB】09-修改tidb客户端访问密码

目录 1、修改配置文件 2、停止tidb-server 3、以root方式启动脚本 4、修改密码 5、停止脚本重启服务 1、修改配置文件 进入tidb-server默认部署位置 #切换tidb账号 su tidb# 进入tidb-server部署路径 cd /tidb-deploy/tidb-4000# 修改配置 vim ./conf/tidb.toml添加内容…

Datawhale AI 夏令营 第四期 AIGC Task3

活动简介 活动链接&#xff1a;Datawhale AI 夏令营&#xff08;第四期&#xff09; 以及AIGC里面的本次任务说明&#xff1a;Task 3 进阶上分-实战优化 这次任务呢&#xff0c;主要是对知识的一个讲解&#xff0c;包括ComfyUI工具的使用啊&#xff0c;以及LoRA的原理啊&…

学习记录第三十天

管道&#xff1a; 无名管道&#xff1a;只能用于亲缘关系进程之间的通信&#xff1a; 有名管道&#xff1a;是一种特殊的文件&#xff0c;存在于内存中&#xff0c;在系统中有对应的名称&#xff0c;文件大小为0字节&#xff1b; 编程&#xff1a; Linux系统中&#xff0c;…

Deepin-获取屏幕缩放比例

Deepin-获取屏幕缩放比例 一、概述二、实现代码 一、概述 环境&#xff1a;UOS、Deepin 我的目的是为了获取屏幕的缩放比例值&#xff0c;就是获取如下的值 我们可以去读取当前的环境变量值&#xff0c;在Qt Creator中可以看到这个值 二、实现代码 相关的Qt接口如下&…

串口通信协议(hal库)

目录 串口通信协议 串行/并行 同步/异步 单工/半双工/全双工 DR寄存器 轮询方式 中断方式 主要中断事件&#xff1a; DMA方式 USART 模块的常用 HAL 库常用接口函数 串口通信协议 串口通信&#xff08;Serial Communication&#xff09;指的是数据通过一个串行的通道…

前端如何使用Nginx代理dist网页,代理websocket,代理后端

本文将指导您如何配置Nginx以代理前后端分离的项目&#xff0c;并特别说明了对WebSocket的代理设置。通过本教程&#xff0c;您将能够实现一次性配置&#xff0c;进而使项目能够在任意局域网服务器上部署&#xff0c;并可通过IP地址或域名访问服务。 笔者建议 先速览本文了解大…

Java、python、php版的企业单位考勤打卡管理系统的设计与实现(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

C语言-使用数组法,指针法实现将一个5X5的矩阵中最大的元素放在中心,四个角分别放四个最小的元素(顺序为从左到右,从上到下,从小到大存放),写一函数实现之。

1.题目要求&#xff1a; 将一个5X5的矩阵中最大的元素放在中心&#xff0c;四个角分别放四个最小的元素&#xff08;顺序为从左到右&#xff0c;从上到下&#xff0c;从小到大存放&#xff09;&#xff0c;写一函数实现之。 2.数组法实现 #define _CRT_SECURE_NO_WARNINGS 1…

【自动驾驶】控制算法(一)绪论与前期准备

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

ROW_NUMBER(), RANK(), DENSE_RANK() SQL排序函数图文详解

ROW_NUMBER(), RANK(), DENSE_RANK() ROW_NUMBER(): 为结果集中的每一行分配唯一的连续编号。即使有重复的值&#xff0c;ROW_NUMBER() 也会为它们分配不同的序号。 SELECT column_name, ROW_NUMBER() OVER (ORDER BY column_name) AS row_num FROM table_name;2. RANK(): 对结…

2-68 基于matlab的小波分解子模式和盒维数的车型识别程序

基于matlab的小波分解子模式和盒维数的车型识别程序&#xff0c;可以选择不同车型&#xff0c;包括小车、中车、大车。GUI可视化界面操作&#xff0c;已包括多种图片。程序已调通&#xff0c;可直接运行。 2-68 小波分解子模式和盒维数 - 小红书 (xiaohongshu.com)

RabbitMQ实现多线程处理接收消息

前言&#xff1a;在使用RabbitListener注解来指定消费方法的时候&#xff0c;默认情况是单线程去监听队列&#xff0c;但是这个如果在高并发的场景中会出现很多个任务&#xff0c;但是每次只消费一个消息&#xff0c;就会很缓慢。单线程处理消息容易引起消息处理缓慢&#xff0…

深度学习(YOLO、DETR) 十折交叉验证

二&#xff1a;交叉验证 在 K 折验证之前最常用的验证方法就是交叉验证&#xff0c;即把数据划分为训练集、验证集和测试集。一般的划分比例为 7&#xff1a;1&#xff1a;2。但如何合理的抽取样本就成为了使用交叉验证的难点&#xff0c;不同的抽取方法会导致截然不同的训练性…

c语言学习,malloc()函数分析

1&#xff1a;malloc() 函数说明&#xff1a; 申请配置size大小内存空间 2&#xff1a;函数原型&#xff1a; void *malloc(size_t size) 3&#xff1a;函数参数&#xff1a; 参数size&#xff0c;为申请内存大小 4&#xff1a;返回值&#xff1a; 配置成功则返回指针&#…

Nginx实验

编译安装 Nginx 准备rhel9环境 下载安装包nginx-1.24.0&#xff08;xftp&#xff09;/复制下载链接 &#xff08;nginx.org——>download&#xff09; 解压 [rootnginx nginx-1.24.0]# tar zxf nginx-1.24.0.tar.gz [rootnginx nginx-1.24.0]#tar zxf nginx-1.24.0.tar.…

[掘金社区]自动签到脚本

直接上脚本 脚本临时写的&#xff0c;今天是运行的第一天&#xff0c;虽然报错编码,但是签到、抽奖都成功了。 下面是修改了之后的版本。 # -*- coding: utf-8 -*- import requests import logginglogging.basicConfig(levellogging.INFO)def check_sign_in_status(base_url,h…

Clobbering DOM attributes to bypass HTML filters

目录 寻找注入点 代码分析 payload构造 注入结果 寻找注入点 DOM破坏肯定是出现在js文件中&#xff0c;我们首先来看源码 /resources/labheader/js/labHeader.js这个源码没什么问题我们重点关注在下面两个源码上 /resources/js/loadCommentsWithHtmlJanitor.js这个源码中重…

STM32cubeMX配置Systick的bug

STM32cubeMX版本&#xff1a;6.11.0 现象 STM32cubeMX配置Systick的时钟&#xff0c;不管选择不分频 还是8分频。 生成的代码都是一样的&#xff0c;代码都是不分频。 即不管选择不分频还是8分频&#xff0c;Systick都是使用的系统时钟 函数调用 HAL_Init() → HAL_Init…

HarmonyOS开发案例:列表场景实例-TaskPool

介绍 本实例通过列表场景实例讲解&#xff0c;介绍在TaskPool线程中操作关系型数据库的方法&#xff0c;涵盖单条插入、批量插入、删除和查询操作。 效果图预览 使用说明 进入页面有insert(单条数据插入)、batch insert(批量数据插入)、query(查询操作)三个按钮&#xff0c;…

【安卓】播放多媒体文件

文章目录 播放音频播放视频 播放音频 在Android中播放音频文件一般是使用MediaPlayer类实现的&#xff0c;它对多种格式的音频文件提供了非常全面的控制方法&#xff0c;从而使播放音乐的工作变得十分简单。 MediaPlayer类中常用的控制方法。 常用方法名描述setDataSource()设…