ElTooltip虚拟触发+initGlobalState实现qiankun主应用与子应用之间组件共享

292 阅读4分钟

这篇文章是基于前面分享过的qiankun微应用系统搭建(qiankun+vite+vue3从零搭建一个微前端架构系统)及主应用与微应用之间通信(qiankun实现主应用与微应用间通信的常用方式)的基础上继续开发分享的,这次将记录一个工作中遇到的一个需求:在微应用中以Tooltip的形式展示一个主应用中的组件。

一、预期效果

image.png

二、方案调研

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还是不适合传组件,更换其他思路。

image.png

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对象包含了矩形区域的相关属性,如leftrighttopbottomwidthheight,这些属性用于描述一个矩形在二维平面中的位置和大小。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>

image.png

这样就实现我们我们的效果啦,如果还有更好的方法,欢迎留言评论区❀❀❀❀❀