前言

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家更简单、无痛的构建一个生产可用微前端架构系统。关于什么是“微前端”,这里就不作过多介绍了,这里默认大家已经具有相关的预备知识。

对于qiankun如何与Vue结合,这篇博客里有详细介绍:基于qiankun的微前端接入笔记

问题与现象

子应用与父应用不断切换的过程中,子应用内的样式突然乱掉。

原因是子应用加载的<style>标签少了,导致遗失了样式。

(如果你检查DOM树发现没有出现这个现象,那有可能是父、子应用相互影响,建议参考官方文档,使用“样式隔离”参数进行解决)

解决

1)第一种修复函数

定义修复函数

/* main.js */
// 定义styleCount用于记录<style>标签数,styleList用于保存<style>节点对象
let styleCount = 0, styleList = []
// 定义一个修复函数
function fixStyleBug() {
  let qiankunStyles = document.querySelectorAll('div[data-qiankun="你的子项目名"] style');
  let len = qiankunStyles.length;
  if (styleCount < len) {
      console.log(`保存style数量:从${styleCount}变为${len}`);
      styleList = Array.from(qiankunStyles)
      styleCount = len
    } else if (styleCount > len) {
      let container = document.querySelectorAll('div[data-qiankun="你的子项目名"]')[0];
      qiankunStyles.forEach(style => { // 移除旧<style>
          container.removeChild(style);
      })
      styleList.forEach(style => { // 重新添加完整<style>
          container.appendChild(style);
      })
  }
}

调用修复函数进行修复

/* main.js */
// 在路由后置守卫里调用修复函数
router.afterEach(() => {
   fixStyleBug();
})

// 在挂载后调用修复函数
export async function mount(props) {
  fixStyleBug()
  render() // 渲染
}
第二种修复函数

定义修复函数

/* main.js */
// 定义styleCount用于记录<style>标签数,styleList用于保存<style>节点对象
let styleCount = 0, styleList = []
// 定义styleDisappearedRecord,表明styleList哪一段部分保存的是已丢失的<style>
let styleDisappearedRecord = {start: 0, end: 0};
function fixStyleBug() {
  let qiankunStyles = document.querySelectorAll('div[data-qiankun="你的子项目名"] style');
  let len = qiankunStyles.length;
  if (styleCount < len) {
      console.log(`保存style数量:从${styleCount}变为${len}`);
      let originalArr = Array.from(qiankunStyles) // 原始数组
      /* 
      调整丢失的<style>与框架新加的<style>的位置,使得丢失的<style>总是位于数组最后,以便与框架的dynamicStyleSheetElements数组保持对应
      即styleList = [...[未丢失样式], ...[新页面新增样式], ...[丢失样式]]  防止把要添加的<style>误加成新<style>
      */
      styleCount = len
      styleList = [...originalArr.slice(0, styleDisappearedRecord.start), ...originalArr.slice(styleDisappearedRecord.end), ...originalArr.slice(styleDisappearedRecord.start, styleDisappearedRecord.end)]
    } else if (styleCount > len) {
      let container = document.querySelectorAll('div[data-qiankun="你的子项目名"]')[0];
      styleDisappearedRecord.start = styleCount - len + 1;
      styleDisappearedRecord.end = styleCount
      styleList.forEach((style, index) => { // 丢失的样式都是从后面开始消失的,所以对比长度,添加上遗漏的
        if (index > len - 1) {
          console.log('添加:', style);
          container.appendChild(style);
        }
      })
  }
}

调用修复函数进行修复

/* main.js */
// 在路由后置守卫里调用修复函数
router.afterEach(() => {
   fixStyleBug();
})

// 在挂载后调用修复函数
export async function mount(props) {
  fixStyleBug()
  render() // 渲染
}

解释与分析

问题原因

实际上,样式乱掉的原因是因为,子应用加载的<style>标签少了,导致遗失了样式。

基本思路

解决这个问题的基本思路,就是我们自己帮它把丢失的<style>标签重新加上。

首先,我们需要知道“样式乱掉了“的时候,判断出这一现象已经发生。前面提到了,样式乱掉的原因在于<style>标签的减少。我们用一个styleCount来记下正常情况下的<style>标签数。再次加载子应用时,将styleCount与从DOM中读取的<style>标签数量进行对比,如果本次子应用加载的标签数比正常数少,那么就认为本次加载的子应用样式已经乱掉了。

其次,我们还需要事先保存下样式正常时的完整样式代码,即完整数量的<style>标签。我们用一个数组styleList将这些<style>节点保存下来即可。每次有新<style>标签增加,需要同步这个数组(确保styleList里保存的是最全、最完整的<style>标签)

当我们判断出本次加载子应用时<style>标签数减少了,就能判定样式存在丢失,接着从styleList这个数组里把丢失的<style>节点再次添加上去,这样遗失的样式就回来了,子应用里的样式也不会再乱了。

注意点

1)在点击子应用里之前未进入过的新页面时,是会增加<style>标签的。因此,需要在此时将框架新增的<style>也保存下来,保证我们的数组styleList是同步保存了全部的<style>标签。当然,标签数styleCount也要进行更新。

​ 因此才会在全局后置路由守卫里加入这个修复函数,以便进入页面时进行styleList、styleCount的更新。

2)修复函数一、修复函数二其实基本原理都是一样的。只是修复函数一是在检测到<style>标签丢失后,将DOM内的原标签全部删除(包括未遗失的),然后重新从styleList给DOM加上(这样就不用考虑具体遗失的是哪部分的标签)。属于比较暴力的方法。

​ 修复函数二是在一的基础上做的优化,不会删除DOM的原标签,只会把遗失的重新补上。但是比较麻烦的是,需要确保知道遗失的是哪部分的标签。这将有一个调整顺序的过程,具体的说明写在了代码注释里。
3)注意把代码里的

 document.querySelectorAll('div[data-qiankun="你的子项目名"]')

这些部分,把“你的项目名”,替换成你实际的子应用项目名。

结语

“我们不能解决问题,但是我们能解决出问题的人。”

目前还未找到<style>标签丢失的原因、以及出现这种现象的条件(极其没有规律)。相当于我们不知道问题在哪,所以也谈不上如何去解决问题本身。

但是换种思路,我们可以不去纠结“问题是怎么出现的”,我们可以把重点放在“如何避免这个现象”。只要样式不再次乱掉,用户用起来体验没影响,我们是可以勉强接受的。

解决不了<style>标签遗失这个问题,那就直接解决掉<style>标签。 ( ̄▽ ̄)"

如果有更好的修复办法,或者找到了这个现象的深层原因,欢迎指教!

Logo

前往低代码交流专区

更多推荐