基于qiankun实现多页签打开微应用缓存方案,通过vue keep-alive实现页面缓存。
实现思路
整体 实现思路 比较简单
1、在微应用的 unmount 的时候 把当前 Vue 实例对象 存到 一个自定义 appCacheMap 对象中, 供下次进入使用;
2、微应用 mount时,将 步骤1 存的实例对象 重新挂载到当前新实例上。
需要注意的是 在mount时, 必须在new Router路由实例,不然路由切换会没有响应。
路由 router-view 需要包上 keep-alive。
qiankun 加载注册子应用 registerMicroApps loadMicroApp 等相关内容,可以点击 官网 查阅。
关于卸载的问题
当同个微应用 其中一个标签 关闭了,再次打开 还是会缓存。
一个微应用 下 已经有打开的页面, 在打开一个新页面(之前已经打开过,关闭了的,缓存了vnode), 打开的新页面 还是命中缓存。
解决思路:
关闭标签的时候 保存当前缓存的标签信息 closeCachedViews,微应用监听路由 根据‘当前的打开标签信息’ 和 ‘关闭保存的标签信息‘ closeCachedViews 判断是否 重新打开的已关闭页面,设置 keep-alive 的 include 移除 不缓存 该页面, 然后 移除 ‘关闭保存的标签信息’closeCachedViews 当前值。
代码内容
1、unmount内容
export async function unmount() {
const needCached = instance.cachedInstance || instance;
const cachedInstance = {};
const _router = instance.$router
cachedInstance._vnode = needCached._vnode;
// keepalive设置为必须 防止进入时再次created,同keep-alive实现
if (!cachedInstance._vnode.data.keepAlive)
cachedInstance._vnode.data.keepAlive = true;
let routeFlatArr = appCacheMap[key]?.routeFlatArr || _router.options.routes.flatMap(item => { return item.children ? [item, item.children] : [item] }).flat()
appCacheMap[key] = {
...cachedInstance,
// $router: _router,
apps: [].concat(_router.apps),
routeFlatArr
// currentRoute: { ..._router.currentRoute },
}
// 卸载实例
// instance.$destroy();
// instance.$el.innerHTML = '';
// 设置为null后可进行垃圾回收
// instance = null;
}
2、mount内容
export async function mount(props) {
// 必须在这里new Router实例,不然路由切换会没有响应
resetRouter()
const appCache = appCacheMap[key] || ''
let vnode = appCache._vnode || null;
if (appCache) {
// appCacheMap实例存在, 使用缓存
// 让当前路由在最初的Vue实例上可用
router.apps.push(...appCache.apps);
// 当前路由path。 router.currentRoute.path 获取不到,只能通过这种方式
let currentRoutePath = location.hash.match(/#\/(\S*)/)[1].replace(/\?.*$/, "")
// 通过path 查找 路由信息
let currentRoute = appCache.routeFlatArr.find(item => item.path === currentRoutePath)
// 保存关闭事的 缓存路由数组
console.log(window.microApp.closeCachedViews)
// 判断 当前打开的 路由path 是否包含在 已关闭的数组中
if (window.microApp?.closeCachedViews && window.microApp.closeCachedViews.includes(currentRoute.name)) {
// 中转刷新页面
proxyRouter.replace({
path: "/redirect" + location.pathname + "#/refresh?currentName=" + currentRoute.name + "¤tPath=" + currentRoute.path + location.search.replace('?', '&')
});
}
}
instance = newVueInstance().$mount(
container
? container.querySelector("#micro-app-child")
: "#micro-app-child"
);
}
// 实例化子应用vue-router
resetRouter(){
router = new Router({
// mode: 'history', // require service support
base: "/app1",
scrollBehavior: () => ({ y: 0 }),
routes: [...],
});
}
// 创建子应用实例
newVueInstance(cachedNode) {
const config = {
router: router, // 在vue init过程中,会重新调用vue-router的init方法,重新启动对popstate事件监听
store,
render: cachedNode ? () => cachedNode : (h) => h(App), // 优先使用缓存vnode
};
return new Vue(config);
}
3、微应用 router-view 路由拦截内容
<template>
<el-main class="scroll-bar">
<keep-alive :include="cachedViews">
<router-view class="page-container" :key="key" />
</keep-alive>
</el-main>
</template>
<script>
export default {
name: "AppMain",
data() {
return {
cachedViews: [],
};
},
mounted() {
this.setViews();
},
watch: {
$route: {
handler() {
this.setViews();
},
deep: true,
},
},
computed: {
key() {
return this.$route.path;
},
},
methods: {
setViews() {
let tmp = window.microApp.cachedViews;
if (
window.microApp?.closeCachedViews &&
window.microApp.closeCachedViews.length &&
window.microApp.closeCachedViews.includes(this.$route.name)
) {
// 匹配到当前打开路由 在 closeCachedViews 关闭内, 不缓存
tmp.splice(tmp.indexOf(this.$route.name), 1);
window.microApp.closeCachedViews.splice(
window.microApp.closeCachedViews.indexOf(this.$route.name),
1
);
}
this.cachedViews = tmp;
},
},
};
</script>
4、中转刷新页
<template>
<div class="app-main" v-loading="true"></div>
</template>
<script>
export default {
data() {
return {};
},
created() {
this.refresh();
},
methods: {
refresh() {
let { query } = this.$route,
{ currentPath, ...otherQuery } = query;
this.$router.replace({ path: "/" + currentPath, otherQuery });
},
},
};
</script>