需求:
A:主应用
B:子应用
项目框架:vue2 + 全家桶 + qiankun
应用间切换需要保存页面缓存(多页签缓存),通过vue keep-alive只能实现页面级缓存,在单独打开的应用里能实现缓存,但是子应用切换到主应用,那子应用的缓存失效
方案:
采用子应用切换时,将子应用作为一个vue实例,再次加载时使用保存的实例上的vnode替换vue实例化的时候的render函数
前提
1.项目已实现页面级缓存(其实就是通过keep-alive include来实现)
解决步骤
1.qiankun加载子应用的方式选择
1.通过registerMicroApps注册子应用,qiankun会通过自动加载匹配的子应用;
2.通过loadMicroApp手动注册子应用
第一种qiankun自动检测路有的变化来控制子应用的卸载和加载,我们无法监听到,也就无法去替换vnode,所以采用第二种,手动加载应用(通过路由的变化去监听下就行了)
//navbar。vau
watch: {
$route(to, from) {
//判断是否要加载 或卸载应用
doQiankun(to, from)
//这段代码时其他功能
if (to.path.indexOf(ssoName) !== -1) {
this.isShowssoAdmin = true
} else {
let timer = setTimeout(() => {
this.isShowssoAdmin = false
clearTimeout(timer)
})
}
}
},
//抽离的js
export const doQiankun = (to, form) => {
if (to.path.indexOf(ssoName) !== -1 && (form.path.indexOf(ssoName) === -1 || !form.path)) {
loadApp()
} else if (to.path.indexOf(ssoName) !== -1 && form.path.indexOf(ssoName) !== -1) {
console.log('子应用之间切换')
} else {
unLoadApp()
}
}
export const loadApp = () => {
// 手动加载微应用
microApp = loadMicroApp({
name: ssoName,
entry: ssoUrl,
container: `#${ssoName}`,
props: { }
})
}
/**
* @description: 手动卸载微应用
* @return {*}
* @Date Changed:
*/
export const unLoadApp = () => {
console.log('手动卸载微应用')
// 手动卸载微应用
if (microApp) microApp.unmount()
}
2.实现手动加载后,在子应用中unmount 卸载周期中存储实例
//main.js
//instance 其实就是new Vue()的实例
//loadedApplicationMap定义的对象
let loadedApplicationMap = {}
let instance = null
export async function unmount() {
unmountCache(instance, loadedApplicationMap, instance.$router)
router = null
}
/**
* @description: qiankun卸载子应用时 保存子应用实例
* @param {*} instance 子应用实例
* @param {*} loadedApplicationMap 保存的对象
* @param {*} _router 离开时的路由
* @return {*}
* @Date Changed:
*/
//代码都不用改 保存一下退出应用前的实例
function unmountCache(instance = {}, loadedApplicationMap = {}, _router) {
// 此处永远只会保存首次加载生成的实例
const needCached = instance.cachedInstance || instance
const cachedInstance = {}
cachedInstance._vnode = needCached._vnode
// keepalive设置为必须 防止进入时再次created,同keep-alive实现
if (!cachedInstance._vnode.data.keepAlive) cachedInstance._vnode.data.keepAlive = true
loadedApplicationMap._vnode = cachedInstance._vnode
loadedApplicationMap.$router = _router
loadedApplicationMap.apps = [].concat(_router.apps)
loadedApplicationMap.currentRoute = { ..._router.currentRoute }
}
3.进入应用时,替换vnode
这里要区分是否是首次进入
光替换node 会导致原来的路由失效,所以同时也要针对路有进行处理
//main.js
let isFirst = true
function render(props = {}) {
const { container } = props
// 这里必须要new一个新的路由实例,否则无法响应URL的变化
router = new VueRouter({
// base: window.__POWERED_BY_QIANKUN__ ? '/ssoAdmin/' : '/',
mode: 'hash',
// mode: 'history',
routes
})
// 这里主要是适配子应用的单独访问和继承访问
let vnode = loadedApplicationMap._vnode || null
if (isFirst) {
// 首次进入应用
routerBeforeEach() //自己定义的路由守卫
instance = newVueInstance(vnode).$mount(container ? container.querySelector('#app') : '#app')
isFirst = false
} else {
// router使用缓存命中
// router = loadedApplicationMap.$router;
// 让当前路由在最初的Vue实例上可用
router.apps.push(...loadedApplicationMap.apps)
routerBeforeEach() //自己定义的路由守卫
instance = newVueInstance(vnode)
// 当前路由和上一次卸载时不一致,则切换至新路由
const { path } = router.currentRoute
const oldPath = loadedApplicationMap.currentRoute.path
if (path !== oldPath) {
//replace 替换 浏览器记录
loadedApplicationMap.$router.replace({ path, query: router.currentRoute.query })
}
instance.$mount(container ? container.querySelector('#app') : '#app')
}
}