[qiankun]-多页签缓存

[qiankun]-多页签缓存

  • 环境
  • 功能需求
  • 多页签缓存方案
    • 方案1.主服务进行html替换
    • 方案2.微服务vnode 替换
    • 方案3.每个微服务都不卸载
      • 微服务加载方式的选择
      • 微服务的路由路径选择
      • 微服务的缓存工具
      • 微服务的容器
        • 使用tab作为微服务的挂载容器
        • 使用微服务路由作为微服务的挂载容器
          • 场景描述
      • 微服务的缓存
        • 缓存微服务
        • 删除微服务
        • 不同路由同一微服务情况

管理系统比较常用的一个功能就时多页签的缓存,我们通过缓存已经打开的页签,并在切换页签的时候看到之前的查询结果,在关闭页签的重新点击菜单那时,看到的是新的没有查询记录界面

环境

语言:采用的主流的vue3.x+ts开发
UI框架:使用的是ant-design框架
其它工具:使用了微服务技术-qiankun
缓存工具:keep-alive

功能需求

  • 通过点击菜单,打开一个菜单页签(点击后展示的菜单称之为页签):

    如果点击菜单之前已打开页签中没有该菜单,则该页面应该是初始页面状态;

    如果点击菜单之前已打开页签中有该菜单,则该页面应该是上次操作后状态;

  • 切换已打开的页签:

    则页面应该都是上次操作后状态;

  • 关闭页签后再次点击菜单:

    因为点击菜单之前关闭页签,所以已打开页签中没有该菜单,则该页面应该是初始页面状态;

多页签缓存方案

方案1.主服务进行html替换

通过存储每个微服务容器的html内容,然后在切回当前页签的时候,微服务容器使用缓存的html替换,也就是所谓的替换innerHTML。

方案尝试结果描述:
替换的内容,使得切换回原有页面确实是上次的查询结果页面,但是页面失去了响应性,变成了静态的HTML页面,包括下拉框都没有了

方案2.微服务vnode 替换

通过存储每个微服务容器的 vnode 内容,然后在切回当前页签的时候,创建微服务实例的时候,使用vnode替换掉原有的render渲染函数

方案尝试结果描述:
因为本人使用的vue3开发,使用render渲染函数是vue2创建vue实例的方法,因此该方案无法实现使用vnode替换掉原有的render渲染函数

尝试了vnode的缓存强制替换的可能性,vue3提示_vnode是只读属性不可以设置

方案3.每个微服务都不卸载

以上两个方案,都是基于同时只加载一个微服务,并且在切换的时候卸载当前微服务,之所以先尝试前两个方案也是因为,如果页面每个微服务都不卸载,当打开的页签过多时会有性能问题,因为微服务总数量暂时有限,该问题此时先不考虑了,如果之后有需要会另外总结

因为前两个方案都不可行,只能尝试该方案,方案描述:

微服务加载方式的选择

qiankun加载加载微服务的方式有两种:

  1. 是注册加载,通过劫持路由,会根据路由变化加载每个微服务
    registerMicroApps,是根据路由的变化加载微服务的,只要路由变化,就会触发微服务重新加载微服务,因此无法阻止微服务的重新加载,缓存不能被使用,指定容器的内容会被覆盖,因此感觉不适合缓存页签的方案
  2. 是手动加载,可以自己决定什么时候加载
    loadMicroApp ,是手动加载,因此能决定是重新加载,还是使用缓存,感觉适合使用该方式

因此选择手动加载微服务的方式

微服务的路由路径选择

主服务具有公共页面,例如404,intro等路由页面,公共页面与微服务都是展示在页面的主体区,并且不需要缓存,因为没有操作功能,当然缓存也是可以的。微服务展示的时候,公共路由页面不可展示,路由也需要变为微服务的路由,不能使用之前的路由路径。

因此可以使用router.push(),或者router.replace()改变浏览器的路由路径,有说可以把路由router想象成一个访问记录的栈,router.replace() 是替换掉栈顶,而router.push() 则是向栈中再堆入一个新记录

考虑过所有微服务使用同一个路由路径,但是因为浏览器的前进后退记录管理服务,因此每个微服务的路由路径不能完全相同,否则无法区分微服务的前进后退记录,所以也不能使用replace方法了

因此每个微服务页面具有一个唯一的路由路径,需要使用router.push记录所有的路由路径,并且需要用来加载微服务的路由组件(如果没有找到指定的路由,系统会跳转404,因此需要微服务的路由组件,微服务众多,因此所有微服务可以采用相同的前缀,用以匹配到同一个路由组件),一个微服务路由组件就能够满足需求

相同的前缀,/micro/XXXX/XXXX,例如:/micro/service/function

{
    path: "/micro/:pathMatch(.*)*",///micro/:projectName/:page
    meta: { auth: true },
    component: () => import(/* webpackChunkName: "micro" */ "../micro/index.vue")
  }

微服务的缓存工具

默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态 —— 当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。

keep-alive 是 vue 内置的动态组件缓存工具

keep-alive 缓存路由的key默认是每个路由的name,一般的路由缓存因为每个路由设置了不同的name,所以include包含需要缓存的路由的name即可

那么针对想更改缓存Key的情况:

<router-view v-slot="{ Component, route }">
  <keep-alive :include="cachePannels">
    <component :is="wrap(route, Component)" :key="wrapKey(route.path)" />
  keep-alive>
router-view>

以下对component与key的处理,PROJECT.cacheKey设置缓存的key字段:

const wrap = (route, component) => {
  component.type.name = "path" == PROJECT.cacheKey ? route.path : store.state.pathMap[route.path].id;
  return component
};
const wrapKey = (path) => {
  if ("path" == PROJECT.cacheKey) {
    return path
  } else {
    return store.state.pathMap[path].id
  }
}

经过测试发现若缓存的字段是path,则缓存成功,若是key是改成id则缓存不成功,因为对于源码的理解并不深入,所以目前并不理解原因,也暂时不做深入,这里还是想吐槽一句,看到在源码处这个问题已经被提出几年了,但是都没有一个官方正式的回应,都是小伙伴提供的各种解决方案,而且就像上面说的绑定的key还必须是path其它不可以。。。

好吧,暂时不纠结了,既然path可用,就确定使用path,以下是最终代码

<router-view v-slot="{ Component, route }">
  <keep-alive :include="cachePannels">
    <component :is="wrap(route, Component)" :key="route.path" />
  keep-alive>
router-view>
const wrap = (route, component) => {
  component.type.name = route.path;
  return component
};

因为微服务使用的是同一个路由组件,因此name相同,那么正好需要使用根据key进行缓存的功能

基于上述原因,使用keep-alive缓存,路由path,作为缓存的key。

关于keep-alive重写的功能以后有机会在尝试吧

微服务的容器

一开始我是倾向于直接用微服务路由组件作为加载微服务的容器

但是使用路由的path作为路由缓存的key,理论讲一旦path发生变更,则路由组件会被销毁重新加载,但是微服务切换时到底组件是否会被销毁?在尝试时存在一些无可避免的问题,当时没有成功

使用tab作为微服务的挂载容器

这里先讲述一个成功缓存的方式


微服务路由 micro.vue



注意,虽然微服务加载是在tab中挂载的,但是router-view此时是要正常加载微服务路由的,因为路径要有对应的匹配路由,但是微服务又不在其中挂载,所以隐藏微服务路由 micro.vue 的展示即可

使用微服务路由作为微服务的挂载容器

加载的时候,因为使用的path作为key,理论来讲,keep-alive应该将每个微服务作为一个单独的路由进行缓存.

但是实际情况有所不同,现象描述:

在打开页签的过程中,每次都是onMount了的,切换的时候,可以看到确实缓存了微服务,每次切换的时候显示的是缓存页面

但是在关闭页签的时候出现了问题,一旦关闭了其中一个正在展示的微服务,其它微服务页面就白屏了,就像是其它路由组件都被删除了?!所以为什么作为单独路由缓存的其它页面没有正常展示?但是如果关闭的是没有展示的页签,则并不影响当前路由的展示与后续的切换

因为该问题没有解决,所以最终没有采用该方式!!下面采用路由组件作为容器时的现象

场景描述

主服务首次点击菜单A,打开页签A,挂载A服务 onMounted onActivated

主服务首次点击菜单B,打开页签B,挂载B服务 onMounted onActivated

切换A服务,B onDeactivated,A onActivated

切换B服务,A onDeactivated,B onActivated

此时关闭B服务:

实际:B onUnmounted,A onMounted onActivated

理论:B onUnmounted,A onActivated

这就像是A挂载到了B的路由组件上了?
正常的切换时keep-alive缓存的组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-61K3ycOm-1677045679175)(2023-01-03-11-37-19.png)]

vue 的组件,不同路径,同一个路由, 缓存的组件为同一个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7wZSirDR-1677045679177)(2023-01-03-15-16-52.png)]

在这里插入图片描述

因此同组件路由卸载的时候,另外的就无法展示了,原因找到了,但是如何区分开?尝试过使用cloneVnode 克隆micro.vue组件,但是无效,所以暂时未找到解决方案

微服务的缓存

缓存微服务

需要缓存的微服务正常手动加载微服务即可,并缓存该微服务

最早的时候是直接缓存的

cacheMap[microApp.path] = loadMicroApp(microApp)

后来在实践中发现直接缓存是存在问题的,例如当loadMicroApp失败的时候(微服务不存在,不可用的情况),此时缓存的微服务实例是不存在的,自然对于该微服务实例后续的卸载会导致报错

因此,当缓存微服务的时候需要确保微服务加载成功:

const appMicro = loadMicroApp(microApp, { singular: false }, microLifeCycle);
appMicro.mountPromise.then((res) => {
  //微服务加载成功时保存微服务
  state.micro.cacheMap[microApp.path] = appMicro;
}).catch((error) => {
  //微服务加载失败时提示错误
  console.error(`mountPromise error`, error)
})

microLifeCycle是微服务的生命周期,可以在其中设置微服务的加载动画

删除微服务

关闭页签,也就是删除缓存微服务时,注意一定要先卸载微服务,unmount,否则下次加载该微服务时会报已挂载错误

//卸载微服务
cacheMap[microApp.path].unmount();
//删除微服务实例
delete cacheMap[microApp.path]

不同路由同一微服务情况

如果微服务有两个路由,而两个路由页面都点击后要缓存,此时也是正常加载即可,当作两个不同微服务加载即可,也不会报已挂载错误,因为路径不同可以当作缓存的是两个微服务(不同于在micro.vue中挂载,该路由组件中如果当作两个微服务挂载是会报已挂载错误的),因为是加载在tab中,彼此并不互相影响

你可能感兴趣的:(前端模块开发,微服务,前端,vue.js)