qiankun保姆级教程

4,110 阅读16分钟

1. 需求分析

1.1 为什么要使用qiankun

首先在我们引入任何一项技术的时候,肯定有他所对应的背景。比如我们使用ElementPlug这款UI库,目的就是提高开发过程中的开发效率。当然qiankun也不例外,他也有自己所对应的使用背景。使用qiankun的大背景基本也就是如下几点:

  • 老项目耦合和很多条业务线,变得越来越庞大,发布版本的时候,build一下需要好久
  • 老项目中存在很多的问题,但是又没有时间对他进行重构
  • 老项目中的框架版本过低,或UI库的版本过低,导致很多功能无法使用
  • 想使用更新的技术栈来开发,跟上时代变更的步伐

基于上述种种原因,qiankun无非就成了拆分业务架构最合适的选择

1.2 qiankun的优势有哪些

这里引用官网对他的描述:

  • 技术栈无关:主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发,独立部署:微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级:在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时:每个微应用之间状态隔离,运行时状态不共享

qiankun官网

环境配置信息

在本文所用的环境配置信息如下:

  • 主应用:Vue3 + ElementPlus
  • 子应用1:Vue2 + Element-UI
  • 子应用2:Vue3 + ElementPlus

配置和使用qiankun

基本的项目创建就不在此做过多的阐述了,网上有很多相应的教程,建议使用VueCli进行创建,开箱即用

基本使用

主应用改造

主应用加载子应用在官网提供了两种加载模式,一种是自动加载,另一种是手动加载,他们的使用场景如下:

  • 主动加载:主动加载是通过监听浏览器URL的变化,自动的加载其所对应的子系统
    • 主动加载更适用于当前业务系统可单独抽离出来,比如在一个销售类的CRM管理系统中,可以将物流模块单独抽离出来
  • 手动加载:手动加载是在主应用中,触发了相应的操作,从而加载子应用
    • 手动加载的个性化定制更强,因为我们可以在触发主应用中某一个操作,或者更改某一个数据的时候,加载子系统
    • 如果有需要同时加载多个子应用的时候,只有手动加载可以做到

但是手动加载和自动加载的差距就在于:

  • 自动加载不需要手动的对子系统进行销毁,而手动加载则相反,手动加载需要手动的对子系统进行销毁
  • 如果需要同时加载多个子应用,那么只有手动加载可以做到

下面就针对于这两种情况,对主应用进行改造,在此之前,建议在引用vue-router的时候,使用历史模式,下文也是基于历史模式所做的讲解

自动加载

首先我们准备一个Vue3版本的主应用,然后执行安装qiankun的命令npm install qiankun -S,然后在主应用中加入如下配置:

重点字段及其含义在注释中标明

// main.js中
import { registerMicroApps, start } from 'qiankun'

registerMicroApps([ // registerMicroApps, 注册微应用
  {
    name: 'qiankun-vue3', // 微应用的名称
    entry: '//localhost:5000', // 微应用的地址
    container: '#sub-app', // 主应用中挂载微应用的Dom节点
    activeRule: "vue3", // 当路由匹配到activeRule的时候,自动加载微应用
    props: {} // 向微应用中传递的参数,稍后会有介绍
  },
  {
    name: 'qiankun-vue2',
    entry: '//localhost:4000',
    container: '#sub-app',
    activeRule: "vue2"
  }
])

// 启动 qiankun
start()

其中的container字段比较重要,这个字段所指的是挂载子应用的dom节点,这个节点不要跟随主应用中页面的变化而消失,一般放在App.vue或者菜单的右侧可视区域中。以菜单为例,假设菜单有如下HTML结构

// Menu.vue
<section>
    <aside>
        <ul>
            <li>菜单1</li>
            <li>菜单2</li>
        </ul>
    </aside>
    <main>
        <!-- 此处为重点 -->
        <div id="sub-app"></div>
        <router-view></router-view>
    </main>
</section>

在上述代码中,我们将Vue的内置组件<router-view />放在了<main>标签中,即页面变化的时候,只在main标签下进行更改。同样,我们也将idsub-app<div>标签放在了<main>中,这样当子应用加载的时候,就会以idsub-app的标签作为容器

手动加载

手动加载就涉及到了一些特定的逻辑。假设我们有如下逻辑,当路由是以/vue2开头的时候,加载vue2的子系统;当路由以/vue3开头的时候,加载vue3的子系统;当路由不是以/vue2或者/vue3开头的时候,分别卸载其子系统,在此,不考虑/vue2/pageOne/vue2/pageTwo的子应用重复卸载在加载的影响,大致实现如下:

// 定义当前的主应用
let activeMicroApp: MicroApp | null = null

// 子应用加载逻辑
router.beforeEach((to, from, next) => {
  // 如果当前主应用存在即卸载
  if (activeMicroApp) {
    activeMicroApp.unmount()
  }
  
  // 当路由以/vue3开头的时候
  if (to.path.startsWith("/vue3")) {
    activeMicroApp = loadMicroApp({
      name: 'qiankun-vue3',
      entry: '//localhost:5000',
      container: '#sub-app',
      props: {}
    })
  }

  if (to.path.startsWith("/vue2")) {
    activeMicroApp = loadMicroApp({
      name: 'qiankun-vue2', // app name registered
      entry: '//localhost:4000',
      container: '#sub-app'
    })
  }
  next()
})

总结

自动加载和手动加载的参数配置,就差在了字段activeRule上,因为在自动加载中,应用是通过监听URL的变化,从而加载子应用。而手动加载则不需要,因为手动加载可以适应其他更复杂的业务逻辑。最后基于手动加载的特性,我们可以实现同时加载多个子系统的需求,这时子应用的注册信息中,只需要将container保持不一致即可

子应用改造

子应用的改造,对于vue2和vue3基本相同,以vue3为例,不一致的地方在下文会做单独说明

  1. 首先第一步:在src目录下新增pablic-path.ts,并书写如下内容
if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  1. 更改main.js
// 引入配置文件
import './public-path.js'

// 引入 vue-router 的方法
import { createRouter, createWebHistory } from 'vue-router'

let router = null
let instance = null

function render(props = {}) {

  router = createRouter({
    routes,
    // 下面的vue3为路由匹配的逻辑,如果在主应用中写的加载本子应用的activeRule为subProject1的时候,此处的vue3变成subProject1
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue3' : '/'),
  })
    
  instance = createApp(App).use(router).use(store).use(ElementPlus, {
    locale: zhCn
  }).mount(props.container ? props.container.querySelector('#app') : '#app')
}

// 如果是单独启动的子文件,保证仍能正常运行
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

// 生命周期的钩子函数
// 导出第一次进入当前子应用的钩子函数
export async function bootstrap() {
}

// 导出每次创建挂载时的钩子函数
export async function mount(props) {
  render(props) // 核心在这里,每次挂载的时候,执行我们所封装的render函数
}

// 导出每次销毁时的钩子函数
export async function unmount() {
  instance.$destroy?.()
  // instance._container.innerHTML = ''
  instance = null
  router = null
}
  1. 更改打包配置文件vue.config.ts
// vue3
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package.json');
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 5000,
    headers: {
    /** 
    这里需要开启允许跨域
    因为我们是在主应用中加载本子应用,本子应用因为使用的另外一个端口号,此时就产生了跨域问题
    跨域问题是由于浏览器的同源策略所造成的,不懂的小伙伴可以了解下跨域相关的知识
    **/
      'Access-Control-Allow-Origin': '*'
    }
  },
  /**
  此处需要将子应用打包成UMD格式
  其中chunkLoadingGlobal字段在官网中叫jsonpFunction
  但是webpack5对jsonpFunction进行更名,更名为chunkLoadingGlobal
  如果你们的项目打包工具不是webpack5,请使用jsonpFunction
  **/
  configureWebpack: config => {
    config.output.library = `${name}-[name]`
    config.output.libraryTarget = "umd"
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
  }
})

总结

到此,基本的配置就已经完成了,其实这部分内容在官网上也都有描述。如果屏幕前的你跟着一步步做下来,已经可以发现子应用已经可以正常的挂载了。最后附上官网改造链接qiankun,重点内容马上就来~

进阶

在讲解进阶方面的知识之前,我们先大致了解下qiankun加载子应用的逻辑是什么。经过上面基础使用部分可以得知,qiankun加载子系统的主要思想就是通过路由匹配,把一个新的vue实例挂载到了一个指定的div上,并且主系统是与子系统共存的。简单来说就是,在一个应用程序中,同时存在了两个vue实例,他们分别是一个主系统和一个子系统(暂时忽略通过手动加载的方式,使得一个页面可以存在多个子系统)。当路由匹配到对应子系统的activeRule的时候,加载这个子系统所对应的js文件。何以证明以上的说法呢,请看下面这张图:

image.png 从这张dom结构图中,我们可以得知,子系统其实就是挂载到了主系统中的某个dom节点上,基于以上认知,我们开始下面的学习~

各系统间的通信

跨系统页面的跳转

经过qiankun进行改造过的系统,可能会存在多个系统,在这儿暂且称为主系统A子系统B子系统C,他们可能存在如下几种路由跳转的情况:

  • 主系统A 跳转到 子系统B,也就是主系统跳转到子系统
  • 子系统B跳回主系统A,也就是子系统跳回主系统
  • 子系统B跳转到子系统C,也就是子系统之间的跳转
  • 主系统A的page1跳转到主系统A的page2,或者子系统B的page1跳转到子系统B的page2,也就是系统内部的跳转

综上所述的几种情况,既然存在了跨系统的调用,那么我们具体该怎么实现呢?

  • 主系统到子系统:
    • 这个比较容易实现,在我们需要跳转到子系统的时候,带上加载子系统时所配置的activeRule即可,如,假设我们加载子系统的activeRule/vue2,那么我们在主系统中只写router.push({ path: "/vue2/pageOne", query: {xxx:xxx} })即可,这里的router是主系统中的router
  • 子系统到主系统
    • 如果子系统跳转到主系统,使用的是子系统的router,这样我们会发现,不管我们怎么书写代码,一定跳不出子系统的router,比如,我们使用子系统的router,写了router.push({ path: "/" }),这样我们会发现,现在浏览器URL上的路径为"localhost:8080/vue2",并且我们不管怎么写,都去除不掉URL上的子系统的activeRule
    • 解决方案一:针对如上问题,我们可以结合HTML5提供的history.pushState()来改变浏览器的URL。PS: 在qiankun中,他监听了浏览器URL的变化,并做了一些处理,所以当URL变化的时候,可以正确的加载子系统
    • 解决方案二:我们可以在子系统中,使用主系统的router进行跳转
  • 子系统到子系统
    • 子系统到子系统的路由跳转,我们也可以使用同子系统到主系统的路由跳转方式
    • 方案一:使用history.pushState()进行跳转
    • 方案二:使用主应用的router进行跳转
  • 系统内部路由跳转
    • 子系统内部的跳转就比较容易了,直接使用子系统的router即可。当然如果不嫌麻烦,也可以使用主系统的router或者history.pushState()
// 主系统跳转到子系统
// vue3
import { useRouter } from "vue-router"
const router = useRouter()
const goVue2 = () => {
  router.push({
    path: "/vue2/pageOne",
    query: {
      name: "张三"
    }
  })
}
// vue2
this.$router.push({
  path: "/vue2/pageOne"
})

-----------------------
// 子系统到主系统
import actions from "@/qiankun/actions.js"

// 方案一: 使用主应用的router
/**
* TODO:actions.parentRouter是主应用中传递过来的主应用的router,稍后在【主应用和子应用的数据传递】部分* 进行讲解
**/
actions.parentRouter.push({
  path: "/"
})
// 方案二:使用history API
history.pushState("", "", "/") // PS: 具体用法可参考MDN

------------------------
// 子系统到子系统
// 方案一、使用主应用的router
actions.parentRouter.push({
  path: "/"
})
// 方案二、使用history API
history.pushState("", "", "/") 

------------------------
// 系统内部的跳转:使用当前系统router的API
// vue2
this.$router.push({ path: "/page1" })
// vue3
const router = useRouter()
router.push({ path: "/page1" })

主应用和子应用的数据传递

主应用到子应用的数据传递比较容易实现,通过qiankun官网我们可以知道,在registerMicroApps也就是注册子应用的API中,有一个字段叫props,通过这个字段,我们可以向子应用中传递数据,但是这个数据他不是响应式的,也就是,当子应用改变这个值的时候,主应用是监听不到的,若想主应用也可监听到,可参考下面【全局状态管理(通信)】部分 既然这个通过props所传递的数据,并不是响应式的,那我们可以用它来干什么呢?其实不然,如果我们传递的是一些方法呢,比如主应用的router、封装好的utils方法等。在上面【跨系统页面的跳转】部分,我们使用到了parentRouter这个方法,而这个方法就是主应用通过props字段传递给子应用的,下面就具体看一下是怎么实现的

// 主应用的main.js
import router from './router'
import { registerMicroApps, start } from 'qiankun'

createApp(App).use(router).mount('#app')

registerMicroApps([
  {
    name: 'qiankun-vue3',
    entry: '//localhost:5000',
    container: '#sub-app',
    activeRule: "vue3",
    props: {
      router // 这里我们就把主应用的router传递给了子应用
    }
  },
  {
    name: 'qiankun-vue2', // app name registered
    entry: '//localhost:4000',
    container: '#sub-app',
    activeRule: "vue2",
    props: {
      router
    }
  }
])
// 启动 qiankun
start()

---------------------

// 子应用的main.js
import actions from "@/qiankun/actions.js"

function render({ 
  container,
  router: parentRouter } = {}) {
  /** 
  * 将router封装到actions中的目的是:
  *  - 如果子系统是vue2的话,我们可以通过vue.prototype.$parentRouter的方式,挂载到vue的实例上
  *  - 如果是vue3的话,无法通过vue2的方式来实现
  *  - 为了统一书写方式,不带来心智负担,所以就统一放到了actions.js下
  **/
  actions.setActions(parentRouter)

  router = createRouter({
    routes,
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue3' : '/'),
  })
    
  instance = createApp(App).use(router).use(store).use(ElementPlus, {
    locale: zhCn
  }).mount(container ? container.querySelector('#app') : '#app')
}

// actions.js
class Actions {
  // 默认值为空 Action
  setActions(parentRouter) {
    this.parentRouter = parentRouter
  }

  parentRouter() {
    return this.parentRouter
  }
}

const actions = new Actions();

export default actions;

子应用间的通信

子应用间的通信,可以通过如下几种方式实现

  • localStorage:本质是通过localStorage做了一层中介,通过访问localStorage的值,来实现通信
  • 全局状态管理(通信) (后面讲解):本质是通过一个发布订阅模式来实现,其实也是让主应用做了一个中介

全局状态管理(通信)

  • 通过路由参数通信:前提是需要发生页面跳转行为 在qiankun中提供了initGlobalState方法,这个initGlobalState方法本质上就是一个发布订阅模式,用于改变全局的数据状态。返回值如下:
  • onGlobalStateChange:监听全局属性状态的改变
  • setGlobalStateChange: 改变全局属性的状态
  • offGlobalStateChange: 取消监听全局属性

具体的API介绍参考官网API

那,假设我们现在有一个需求,我们希望主系统向子系统派发一个数据,这个数据由namecount组成,具体需求如下:

Snipaste_2023-04-25_21-32-59.png

  • 当点击子系统中【更改全局vuex数据按钮】的时候,子系统中的“主应用中的信息-〉姓名:XXX,数量XXX”和主应用中的"主应用中的数据-> 姓名:XXX,数量XXXX"同步去更改,并且保持一致
  • 同理,当点击主系统中的【更改数据】按钮的时候,也触发上述现象
  • 综上:简称为主应用和子应用数据同步

分析完我们的小需求后,就开始着手实现了,其实这个需求,本质上也是使用了qiankun提供的initGloablState这个API

  • 首先第一步,我们先看主应用中的更改
// 在src目录下,新建qiankun文件夹,然后创建一个actions.ts的文件(当然你也可以根据喜好自定义)

// 第一步、actions.js
import { initGlobalState, MicroAppStateActions } from "qiankun"

// 初始化 state
const initState = {
  name: "张三",
  count: 200
}

/**
* 这里我们通过qiankun提供的api,创建了一个actions对象,并把它暴露了出去
**/
const actions: MicroAppStateActions = initGlobalState(initState)
export default actions

------------------

// 第二步、在主应用中需要的地方,增加事件监听器,在我的项目里叫Menu
// Menu.vue
<template>
  <div class="header">
    <h1>主应用的数据 -> 姓名: {{ state.name }}, 数量: {{ state.count }}</h1>
    <el-button type="danger" @click="changeMainState">改变数据</el-button>
  </div>
</template>

<script lang="ts" setup>
import actions from "@/qiankun/actions"
import { reactive } from "vue"

// 这里初始化了一个数据源
const state = reactive({
  name: "",
  count: 0
})

// 这里使用了initGlobalState的返回值actions,并通过他提供的onGlobalStateChange方法,对数据进行监听
actions.onGlobalStateChange((currentState) => {
  state.name = currentState.name
  state.count = currentState.count
}, true) // 这里的true表示初始化的时候,就执行一次,有点类似于vue中的watch

const changeMainState = () => {
  // 同样这里也是使用initGlobalState提供的setGlobalState方法,来改变全局的数据
  actions.setGlobalState({
    name: "王武",
    count: state.count + 10
  })
}
</script>

至此,主应用就改造好了,或许你有一个问题,这个initGlobalState的返回值,不需要传递给子应用吗?其实,这个东西,在qiankun内部,已经封装好了,我们直接用就好

  • 第二步、改造子应用
// 第一步、更改main.js
// 这里的actions是我们自己创建的一个类,目的是方便我们在系统中调用,在第二步中讲解
import actions from "@/qiankun/actions.js"
/** 
* 这里的onGlobalStateChange,setGlobalState,offGlobalStateChange
* 是qiankun自动帮我们注入的,所以直接使用即可
**/
function render({ 
  container,
  onGlobalStateChange,
  setGlobalState,
  offGlobalStateChange,
  router: parentRouter} = {}) {

  // 缓存主应用传过来的actions
  const parentActions = {
    onGlobalStateChange,
    setGlobalState,
    offGlobalStateChange
  }
  actions.setActions(parentActions, parentRouter)

  router = createRouter({
    routes,
    history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue3' : '/'),
  })
    
  instance = createApp(App).use(router).use(store).use(ElementPlus, {
    locale: zhCn
  }).mount(container ? container.querySelector('#app') : '#app')
}

-------------

// 第二步、在src的qiankun文件夹下,创建actions.js(当然你也可以按着自己的喜欢换个名称)
// actions.js
function emptyAction() {
  // 提示当前使用的是空 Action
  console.warn("Current execute action is empty!");
}

class Actions {
  // 默认值为空 Action
  actions = {
      onGlobalStateChange: emptyAction,
      setGlobalState: emptyAction,
      offGlobalStateChange: emptyAction
  };

  setActions(actions, parentRouter) {
    this.actions = actions
    this.parentRouter = parentRouter
  }

  onGlobalStateChange() {
      return this.actions.onGlobalStateChange(...arguments);
  }

  setGlobalState() {
      return this.actions.setGlobalState(...arguments);
  }

  offGlobalStateChange() {
    return this.actions.offGlobalStateChange(...arguments)
  }
  
  parentRouter() {
    return this.parentRouter
  }
}

const actions = new Actions();

export default actions;

--------
// 第三步、创造页面
// pageOne.vue
<template>
  <h1>vue3 子应用</h1>
  <h2 class="parent">主应用中的信息 -> 姓名: {{ state.name }} count:{{ state.count }}</h2>

  <el-button-group>
    <el-button type="danger" @click="commitGlobalVuexCount">更改主应用vuex数据</el-button>
  </el-button-group>
</template>

<script setup>
import actions from "@/qiankun/actions.js"
import { reactive, ref } from "vue"

const form = reactive({
  name: "",
  position: ""
})

const state = ref()

// 这里也是通过actions,拿到了onGlobalStateChange方法,对数据进行监听
actions.onGlobalStateChange(parentState => {
  state.value = parentState
})

const commitGlobalVuexCount = () => {
  // 提交更改
  actions.setGlobalState({
    name: "张三",
    count: state.value.count + 1
  })
}
</script>

至此,全局的通信功能就结束了~

总结

至此,进阶篇的讲解就结束了,学会上面这些,基本也可应付绝大部分业务场景

架构设计

重点:从这里开始呢,纯粹只是笔者自己的思考,大家做个参考即可,如果你有更好的解决方式,欢迎评论区一起讨论~

系统拆分的思考

通常,我们所维护或开发的系统是以业务区分的,而业务可能又分为多条产品线,比如常见的销售、售后、物财务等。这里建议以产品线维度进行拆分,并且这条产品线可独立的形成一个闭环。这样的好处就是,所有跟这个产品相关的页面,我们可以放在一起进行维护,而形成这个产品的时候,必定会存在一些仅这个业务所需要的业务组件,这样的拆分模式就很利于我们之后的维护。

如果,我们的系统业务线很多,但是条业务线又很短,这样要怎么拆分呢?

其实,这里如果按着领域驱动的概念来讲的话,每条业务线都是单独的一个领域,而这个领域下,又有很多组成这个领域的子域。举个不太恰当的例子,假设我们有一个销售系统,这个销售系统就是d单独的领域,而这个销售系统又由很多个页面组成,那么这些页面就是子域;说了这么多,到底该怎么做呢?其实更建议单条业务线单个的进行拆分,因为系统是会不断的进行迭代和发展的,说不定哪天,我们现在所看到的“小系统”就会变成庞大无比的“巨无霸系统”

再者,我们讨论下主应用到底该放点啥?其实主应用在很多地方又叫做基座,顾名思义,如果拿盖房子来举例的话,他更像是地基,可以说,他的好坏从一定程度上也可以决定上层建筑。所以,笔者认为,主应用中,只存放全局配置相关的信息,比如:

  • 菜单页,可以控制页面跳转到不同的系统
  • 权限获取,主应用中获取权限信息,然后传递给子应用
  • 全局共享的数据和方法(当然这个公共的Utils也可以的单独打包然后发布到npm)
  • 登录功能和角色信息,一般用户的角色信息是不会经常变动的,所以我们获取一次,之后存起来就好

组件库的引入方式

这里,组件库的引入方式有两种情况

  • 子系统和主系统共用一套组件库,而子系统的组件库是通过主系统传递过来的,这样的好处就是总打包体积会很小,坏处也很明显,如下所述:
    • 这样的问题就是,假如我们框架是使用vue2和vue3两个版本,那么他们的UI库大概率分别为Element-UIElementPlus,尽管vue3对vue2做了兼容处理,但是,也有兼容不好的地方,如果在实际的运行过程中,出现了兼容问题,那么或许这个问题是很难排查或解决的
    • 因为子系统的UI库是来自于主系统,那子系统单独运行的时候,就会因为得不到UI库而跑不起来
  • 主系统和子系统各自使用自己的UI库,互不掺和,这样的好处就是,其中一个子系统如果需要更高的UI库版本,那么只升级他自己的UI库即可,其他子系统或者主系统不受影响,坏处如下所述:
    • 因为我们每个子系统都有各自的UI库,所以总打包体积会相对大一些
    • 主系统加载子系统的时候,需要额外增加加载子系统的UI库,这样,总体的加载时间会变得相对较长(但是如果是按需引入,并且使用的组件并不多的话,其实几乎可以忽略不计)

当然,经过qiankun进行拆分后,可能会存在一个业务组件A写在了业务系统1中,但是,同时业务系统2也需要这个组件,这时,我们对这个含有业务属性的组件,总不能维护多套代码吧,要不在日后维护中会很麻烦,以至于变成一颗“定时炸弹”,这时,我们可以考虑封装一套数据自己的业务组件库,这个会在之后的文章中更新 -- 《如何搭建属于自己的UI库》

综上:其实更建议每个子系统都单独引入自己所需要的UI库,这样可避免其中一个子应用要更新UI库版本,而迫使所有系统都被更新

子系统的keep-alive

观察仔细的小伙伴发现了,这里指的是子系统的keep-alive,而不是主系统或者主系统和子系统的keep-alive,原因如下:

  • 主系统其实是始终不会卸载的,他会一直存在于页面中;但是,子系统却不太一样,如果是通过自动加载的方式实现的微前端,那么子系统的加载逻辑会跟页面的路径有关,也就是说,当页面路径跳到其他子系统的时候,当前子系统就会卸载,那么他所缓存的页面dom也会随之消失
  • 如果在你的系统中,不存在跨系统的页面跳转,那你一定不会出现keep-alive这个问题,因为你的子系统,从始至终就没有被卸载
  • 如果你的系统存在在页面跳转时,需要跨系统的场景,但是这种场景并不多的话,其实,你可以将数据存入主系统中,子系统挂载的时候,再进行初始化数据,当然这种方案比较麻烦,需要手动存数据,所以他只适用于跨系统页面跳转较少的情况
  • 如果你的系统存在大量跨系统的交互时,或许你可以使用手动加载的方式来实现,因为手动加载需要主动卸载当前子应用如果你不卸载当前子应用的话,当前子应用就会一直存在,继而不会触发keep-alive丢失的场景

其实,我也是头疼在了keep-alive的解决方案上,目前没有找出一种特别完美的解决方案,如果有小伙伴对这块比较了解的话,可以私信教我下,相当感谢~

后记

当主系统是使用ElementPlus而子系统是使用Element-UI的时候,会存在UI样式冲突的问题,这时可以使用ElementPlus自定义命名空间来解决,为什么不使用qiankun提供的样式隔离方案?因为在实际使用的过程中,会存在一些问题(报错问题),感兴趣的小伙伴可以自己试试~

小彩蛋