落地qiankun的诸多问题----多tab标签页切换缓存

1,095 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

摸鱼实在是摸得慌了,动手记录最近公司落地的微前端框架所遇到的诸多问题。

本文主要记录公司实现父子应用切换tabs时缓存页面的解决方案

如有什么不对的,欢迎指出~

微前端是什么?微前端有什么用...

这里就不介绍了,还请各位自行百度吧!

项目框架

vue + qiankun2.0 + vuex + vue-router + elementUi

主应用和子应用都是使用了vue,路由统一使用了Hash模式

原理

监听页面路由,判断是否是子应用路由(子应用路由有特定前缀micro)并且是否没有加载过子应用,是则手动加载子应用。 移除tab时,判断是否是子应用菜单并且在tabList中是否还有子应用的菜单,如果没有子应用菜单, 则执行unmount方法卸载子应用,如果有子应用菜单,则执行子应用提供的update方法清除该子应用的组件缓存

实现

主应用

通过监听主应用路由的beforeEach方法来动态加载(loadMicroApp)微应用页面,使用vuex管理已加载的tab标签和微应用数据

router.js

...
const router = createRouter({
  history: createWebHashHistory(process.env.BASE_URL),//hash模式
  routes
})
// 全局前置守卫
router.beforeEach((to, _form, next) => {
  if (to.fullPath === _form.fullPath) {
    return
  }
  store.dispatch('openTab', to)  //调用tab打开新标签方法
  next()
})

store/tabs.js

import router from '@/router/index.js'
import { loadMicroApp, initGlobalState } from 'qiankun'

// state
const state = {
  microAppMap: null,
  tabsList: [],
  currentTab: '-1',
  activeMenuPath: '',
  welcomeTab: {
    url: '/welcome',
    permId: '-1',
    permCode: 'welcome',
    permName: '欢迎页'
  }
}

// actions
const actions = {
  openTab({ rootState, dispatch }, to) {
    const currentPath = to.meta.activePath || to.path
    // 获取菜单列表数据
    const allFunctionList = rootState.user.user.funPerms || []
    // 获取url对应的菜单item数据
    const currentTabItem = allFunctionList.find(i => i.url === currentPath)
    if (currentPath !== state.welcomeTab.url) {
      dispatch('addTab', currentTabItem)
    } else {
      dispatch('addTab', state.welcomeTab)
    }
    if (currentPath.includes('/micro')) {
      dispatch('loadMicroApp')
    }
  },
  // 添加tab
  addTab({ commit }, tabItem) {
    //是否有打开标签
    if (!state.tabsList.some(t => t.permId === tabItem.permId)) {
      commit('ADD_TAB', tabItem)
    }
    commit('SET_CURRENT_TAB', tabItem)
  },
  // 点击tab
  selectTab({ commit }, tabIndex) {
    const currentTab = state.tabsList[tabIndex]
    commit('SET_CURRENT_TAB', currentTab)
    router.replace({ path: currentTab.url })
  },
  // 移除tab
  removeTab({ commit, dispatch }, tabVal) {
    const index = state.tabsList.findIndex(item => item.permId === Number(tabVal))
    const currentPath = state.tabsList[index].url  //当前tab标签的地址
    const previousPath = state.tabsList[index - 1].url  //上一个tab标签的地址
    commit('SET_CURRENT_TAB', state.tabsList[index - 1])
    commit('REMOVE_TAB', index)
    router.replace({ path: previousPath })
    if (currentPath.includes('/micro')) {
      // 如果是子应用地址,触发卸载子应用action
      dispatch('unloadMicroApp', currentPath)
    }
  },
  // 加载微应用
  async loadMicroApp({ state, rootState, getters, dispatch }) {
    // 如果还未加载过,则手动加载微应用
    if (state.microAppMap === null) {
      state.microAppMap = loadMicroApp({
        name: 'micro-admin',
        entry: 'http://localhost:3333',//子应用地址
        container: '#appContainer'
      })
    }
  },
  // 卸载微应用
  unloadMicroApp(_, path) {
    // 如果当前tablist没有微应用菜单,则卸载微应用
    if (!state.tabsList.some(item => item.url.includes('/micro'))) {
      try {
        state.microAppMap.unmount()
        state.microAppMap = null
      } catch (error) {
        console.log(error)
      }
    } else {
      // 否则执行update方法,子应用去除该模块keep-alive缓存
      state.microAppMap.update({
        routerEvent: {
          path: path,
          type: 'close'
        }
      })
    }
  }
}

// mutations
const mutations = {
  ['ADD_TAB'](state, data) {
    state.tabsList.push(data)
  },
  ['REMOVE_TAB'](state, index) {
    state.tabsList.splice(index, 1)
  },
  ['SET_CURRENT_TAB'](state, tabItem) {
    state.currentTab = tabItem.permId.toString()
    state.activeMenuPath = tabItem.url
  }
}

export default {
  state,
  actions,
  mutations
}

子应用

组件开启keep-alive,并且在qiankun提供的生命周期钩子update里调用子应用删除缓存的方法 需要传入一个routeEvent对象,用于清除子应用组件keep-alive缓存

main.js

qiankun渲染方法
function render(props = {}) {
  const { container } = props
  // const history = createWebHashHistory(
  //   window.__POWERED_BY_QIANKUN__ ? `/micro-account` : '/'
  // )
  router = createRouter({
    history: createWebHashHistory(process.env.BASE_URL),
    routes
  })

  router.beforeEach((to, form, next) => {
    // 如果有name则是子应用路由,没有则是父应用路由
    if (to.name) {
      store.commit('PUSH_KEEPALIVE_LIST', to.name)
    }
    next()
  })
// update方法去除子节点菜单的keep-alive状态
export async function update(props) {
  let { routerEvent } = props
  if (routerEvent) {
    switch (routerEvent.type) {
      case 'close':
        {
          store.commit(
            'CLOSE_KEEPALIVE_LIST',
            routes.find(item => item.path === routerEvent.path)
          )
        }
        break
    }
  }
}

store/tabs.js

const state = {
  keepAliveList: []
}

const mutations = {
  ['PUSH_KEEPALIVE_LIST'](state, val) {
    if (!state.keepAliveList.find(item => item === val)) {
      state.keepAliveList.push(val)
    }
  },
  ['CLOSE_KEEPALIVE_LIST'](state, val) {
    state.keepAliveList = state.keepAliveList.filter(item => item !== val.name)
  }
}

export default {
  state,
  mutations
}

最后

项目已经上线了。 如果你也需要实现微应用多tab标签页路由切换,可以尝试一下,如有问题可以找我。

接下来还写一下其他问题的解决方案~

请期待