这篇文章是基于前面分享过的qiankun微应用系统搭建(qiankun+vite+vue3从零搭建一个微前端架构系统)及主应用与微应用之间通信(qiankun实现主应用与微应用间通信的常用方式)的基础上继续开发分享的,这次将记录一个工作中遇到的一个需求:在微应用中以Tooltip的形式展示一个主应用中的组件。
一、预期效果
二、方案调研
1. 使用公共组件库(不适用)
- 这里我演示的例子比较简单,但是在真实项目中,tooltip中要展示的内容比较复杂,并且和业务关系紧密,直接放到通用组件库,就实用性和针对性而言,显然不是一个很好的选择。
2. 使用props将组件传到微应用(不推荐)
- 一开始在想
props既然可以传数据,那传组件过去应该也没什么问题,尝试写了一个简单的demo,共享组件里只有一个el-button,但这种情况下仍然出问题了
主应用:
{
name: 'silvia-micro', // app name registered
entry: '//localhost:5174', // 微应用的出口地址
container: '#container', // 微应用挂载的容器id
activeRule: '/silvia-micro', // 微应用激活路由规则
props: {
mainInfo: {
name: 'zhangsan'
},
SharedTooltip //这是需要共享的组件,组件很简单就写了一个el-button
}
}
子应用:
import actions from './actions';
const SharedTooltip = actions?.actions?.SharedTooltip;
...
<SharedTooltip :visible="true"></SharedTooltip>
- 效果:在子应用显示的时候样式丢失了,肯定是
qiankun默认的样式隔离机制造成的。但是我在尝试使用experimentalStyleIsolation: false关闭样式隔离(这种方式会增加样式冲突的风险,需要谨慎评估和处理)的时候,仍然没有生效,还是找不到样式(有大佬能帮忙解答这个问题将不胜感激!),感觉props还是不适合传组件,更换其他思路。
3. ElTooltip虚拟触发+initGlobalState(推荐!)
3.1 ElTooltip虚拟触发介绍
有时候我们想把 tooltip 的触发元素放在别的地方,而不需要写在一起,这时候就可以使用虚拟触发。这就比较符合我们的需求,触发的元素在子应用,触发后显示的元素在主应用。
3.2 initGlobalState介绍
initGlobalState是qiankun官方提供的主应用与子应用通信方式,详细的我在这篇文章(qiankun实现主应用与微应用间通信的常用方式)讲过,感兴趣可以阅读了解一下。
3.3 具体实现
3.3.1 主应用中新建一个sharedTooltip.vue文件:
-
el-tooltip虚拟触发与普通触发的区别是要加virtual-triggering,:virtual-ref="triggerRef"及动态绑定visible。 -
通过
getBoundingClientRect获取元素位置
getBoundingClientRect是一个 JavaScript 中的 DOM API 方法,它返回一个 DOM 元素的大小及其相对于视口(viewport)的位置信息。这个方法在处理页面布局、元素定位以及碰撞检测等场景中非常有用。例如,在一个很长的页面中有一个固定位置的导航栏,当页面滚动时,可以通过getBoundingClientRect来检测导航栏是否即将离开视口,从而决定是否显示返回顶部按钮等操作
<template>
<el-tooltip v-model:visible="visible" content="Bottom center" placement="right" effect="light" trigger="click"
virtual-triggering :virtual-ref="triggerRef">
<template #content>
<div>这是微应用传过来的图片:</div>
<img :src="imgUrl" alt="123" />
</template>
</el-tooltip>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
interface IProps {
visible: boolean; //是否显示
imgUrl: string; //图片地址
position: { //tooltip显示的位置
top: number;
left: number;
bottom: number;
right: number;
}
}
const props = defineProps<IProps>();
const visible = computed(() => props.visible);
const triggerRef = ref({
getBoundingClientRect() {
return props.position
},
})
</script>
3.3.2 主应用App.vue中使用并监听全局数据状态变化
DOMRect.fromRect介绍:是一个用于创建DOMRect对象的静态方法。DOMRect对象包含了矩形区域的相关属性,如left、right、top、bottom、width和height,这些属性用于描述一个矩形在二维平面中的位置和大小。DOMRect.fromRect方法可以从给定的一组参数创建一个新的DOMRect对象,这在需要手动构建矩形描述对象时非常有用。
<script setup lang="ts">
...;
const visible = ref(false);
const url = ref('');
const position = ref({
top: 0,
left: 0,
bottom: 0,
right: 0,
});
actions.onGlobalStateChange((state) => {
visible.value = state.visible;
url.value = state.imgUrl;
position.value = DOMRect.fromRect({
width: 0,
height: 0,
x: state.position.x,
y: state.position.y,
});
});
</script>
<template>
<header>
<div class="wrapper">
<HelloWorld msg="主应用" />
<el-button @click="click">点击向子应用发送消息</el-button>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/silvia-micro">Micro</RouterLink>
</nav>
<SharedTooltip :visible="visible" :position="position" :img-url="url" />
<div id="container"></div>
</div>
</header>
<RouterView />
</template>
<style scoped>
3.3.3 子应用App.vue中使用
- 新增一个按钮,监听鼠标移出移出来改变tooltip显示与隐藏状态
- 通过setGlobalState改变全局数据状态,这样主应用就能监听到并拿到子应用传递来的数据
<script setup lang="ts">
...;
const handleMouseenter = (e: any) => {
window.console.log('ss', actions);
actions.setGlobalState({
visible: true,
imgUrl: 'https://u7.iqiyipic.com/image/20240228/3c/06/pv_6189210870718500_d_601_480_270.jpg',
position: {
x: e.clientX,
y: e.clientY,
}
});
};
const handleMouseLeave = () => {
actions.setGlobalState({
visible: false
});
};
</script>
<template>
<header>
<h2>我的微应用</h2>
<el-button @click="click">点击向主应用传递数据</el-button>
<el-button @mouseenter="handleMouseenter" @mouseleave="handleMouseLeave" class="tooltip-btn">
共享主应用tooltip组件</el-button>
</header>
<RouterView />
</template>
这样就实现我们我们的效果啦,如果还有更好的方法,欢迎留言评论区❀❀❀❀❀