vue3+vite+microapp创建基座应用,umd模式加载多个子应用的菜单,当加载某个子应用的页面后,点击push到其他页面或其他子应用页面,无法加载变空白了?

vue3+vite+microapp创建基座应用,umd模式加载多个子应用的菜单,当点击加载某个子应用(使用了pina持久化和sessionStorage缓存)的页面时,点击push到其他页面或其他子应用页面,新路由无法加载变空白了(但是刷新浏览器的话却可以加载内容)?
image.png
image.png
想请教下这个左侧路由切换子应用,子应用路由容器为空,无法加载渲染是啥原因呢?子应用卸载函数的问题导致的?
下面是此子应用的main.ts代码,



import { createApp } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

import App from "./App.vue";
// import router from './router'
import { createRouter, createWebHashHistory } from "vue-router";
import routes from "./router";

import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import { _sessionStorage } from "./utils/lib/storage";
import { cacheKeyEnum } from "./enums/cacheKeyEnum";

/* ----------------------分割线-umd模式--------------------- */
declare global {
  interface Window {
    __MICRO_APP_ENVIRONMENT__?: boolean;
    __MICRO_APP_BASE_ROUTE__?: string;
    __MICRO_APP_NAME__?: string;
    microApp?: any;
    mount?: () => void; // 添加这一行,声明全局变量
    onmount?: () => void; // 添加这一行
    unmount?: () => void; // 添加这一行
  }
}

let app: any = null;
let router: any = null;
let history: any = null;
// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行
window.mount = () => {
  console.log("交接班 mount");

  history = createWebHashHistory(
    window.__MICRO_APP_BASE_ROUTE__
      ? `/#${window.__MICRO_APP_BASE_ROUTE__}`
      : "/"
  );
  router = createRouter({
    history,
    routes,
  });

  //导航守卫
  router.beforeEach((to, from, next) => {
    if (to.meta.requireAuth) {
      const _token = _sessionStorage.get(cacheKeyEnum.TOKEN); //是否有登录后的token缓存
      if (_token != null) {
        next(); //放行
      } else {
        //转到登录页
        next({ path: "/login" });
      }
    } else {
      next();
    }
  });

  app = createApp(App);

  // app.use(createPinia())
  const pinia = createPinia();
  pinia.use(piniaPluginPersistedstate); //持久化
  app.use(pinia);
  app.use(router);

  //从 @element-plus/icons-vue中导入所有图标并进行全局注册
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component);
  }
  app.mount("#app");

    //微前端环境
  if (window.__MICRO_APP_ENVIRONMENT__) {
    // console.log('微应用vite渲染了 -- UMD模式')
    // handleMicroData()
  }
};

// 👇 将卸载操作放入 unmount 函数,子应用卸载时会自动执行
window.unmount = () => {
  console.log("交接班 unmount");
  // 安全卸载应用
  if (app) {
    app.unmount();
    history.destroy();
    app = null;
    router = null;
    history = null;
    _sessionStorage.clear(); //清空sessionStorage
    console.log("交接班 子应用卸载完成");
  }

    // 清空当前子应用的所有绑定函数(全局数据函数除外)
  if (window.__MICRO_APP_ENVIRONMENT__ && window.microApp) {
    window.microApp.clearDataListener()
  }
};

// 如果不在微前端环境,则直接执行mount渲染
if (!window.__MICRO_APP_ENVIRONMENT__) {
  window.mount();
}

----20251203补充-----

                <router-view :key="route.path"></router-view>
                <!-- 添加唯一key,native路由模式切换子应用页面时,促使组件重新渲染 add2025-12-03 -->
                <!-- <router-view v-slot="{ Component, route }">
                    <transition name="fade-transform" mode="out-in">
                        <keep-alive :include="cachedViews">
                            <component :is="Component" :key="route.path" />
                        </keep-alive>
                    </transition>
                </router-view> -->

大佬,我今天又试了下,好像不是子应用main.ts卸载的问题,是主应用路由挂载那里,昨天是下面那种写法,当首次加载那个子应用的页面后,后续切换此子应用其他页面或者其他子应用页面都会白屏,但是未进入此子应用,其他子应用切换加载没问题,今天主应用路由挂载那里改成上面那种写法,那个子应用切换加载好像可以啦,这大概是啥原因呢(cachedViews---暂时为空数组)?

阅读 650
1 个回答
import { createApp } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

import App from "./App.vue";
import { createRouter, createWebHashHistory } from "vue-router";
import routes from "./router";
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
import { _sessionStorage } from "./utils/lib/storage";
import { cacheKeyEnum } from "./enums/cacheKeyEnum";

declare global {
  interface Window {
    __MICRO_APP_ENVIRONMENT__?: boolean;
    __MICRO_APP_BASE_ROUTE__?: string;
    __MICRO_APP_NAME__?: string;
    microApp?: any;
    mount?: () => void;
    unmount?: () => void;
  }
}

let app: any = null;
let router: any = null;
let pinia: any = null;
const APP_ROOT_ID = "#app";

// helper: 只清理本子应用写入的 sessionStorage keys
function clearOwnStorage() {
  const prefix = window.__MICRO_APP_NAME__ ? `${window.__MICRO_APP_NAME__}-` : "subapp-";
  Object.keys(sessionStorage).forEach((k) => {
    if (k.startsWith(prefix)) {
      sessionStorage.removeItem(k);
    }
  });
}

// Pinia persisted key factory(确保每个子应用存储 key 隔离)
function createPersistedPlugin() {
  return piniaPluginPersistedstate({
    key: (id) => {
      const appName = window.__MICRO_APP_NAME__ || "subapp";
      return `${appName}-${id}`;
    },
  });
}

window.mount = () => {
  console.log("[micro] mount start", window.__MICRO_APP_NAME__);

  // 每次 mount 都新建 history/router/pinia/app 实例
  const base = window.__MICRO_APP_BASE_ROUTE__ || "/";
  const history = createWebHashHistory(base);
  router = createRouter({ history, routes });

  // 导航守卫(示例)
  router.beforeEach((to, from, next) => {
    if (to.meta?.requireAuth) {
      const _token = sessionStorage.getItem(`${window.__MICRO_APP_NAME__}-${cacheKeyEnum.TOKEN}`);
      if (_token) next();
      else next({ path: "/login" });
    } else next();
  });

  app = createApp(App);

  // Pinia:每次 mount 新建并使用隔离 key 的 persisted 插件
  pinia = createPinia();
  pinia.use(createPersistedPlugin());
  app.use(pinia);
  app.use(router);

  // 注册 Element icons(如需)
  for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component);
  }

  // mount 到容器
  app.mount(APP_ROOT_ID);

  console.log("[micro] mount finished", { base, router, pinia });
};

window.unmount = () => {
  console.log("[micro] unmount start", window.__MICRO_APP_NAME__);

  try {
    // 1) 卸载 vue app
    if (app) {
      app.unmount();
    }

    // 2) 清空挂载节点 DOM 内容(彻底清理 vnode 残留)
    const root = document.querySelector(APP_ROOT_ID);
    if (root) {
      root.innerHTML = "";
    }

    // 3) 清理 pinia 持久化产生的本应用存储(仅本 app 的 key)
    clearOwnStorage();

    // 4) 清理 micro-app 监听(如果有)
    if (window.microApp && typeof window.microApp.clearDataListener === "function") {
      window.microApp.clearDataListener();
    }

    // 5) 释放引用,GC 可回收
    app = null;
    router = null;
    pinia = null;

    // 6) 其他需要清理的:全局事件 / timers / websocket(如果你在子应用注册了)
    // e.g.
    // window.removeEventListener('resize', yourHandler)
    // clearInterval(yourTimer)
    // if (ws) { ws.close(); ws = null; }

    console.log("[micro] unmount finished");
  } catch (err) {
    console.error("[micro] unmount error", err);
  }
};

// 非 micro 环境下,直接 mount
if (!window.__MICRO_APP_ENVIRONMENT__) {
  window.mount();
}

从你这个代码看大概率属于以下情况:

  1. history 未被真正销毁

你调用的 history.destroy() 很可能没有彻底清掉旧的路由实例,导致旧的 history 残留在内存里。
第一次挂载没问题,但卸载后旧的 vnode / history 仍然存在;第二次挂载时 Vue 再往同一个 DOM 挂载,就会出现空白页或路由不跳。

  1. sessionStorage

你使用 _sessionStorage.clear() 清空了整个 sessionStorage,但 Pinia 的持久化插件还会记住旧的 store 状态。
这会导致旧数据(例如 token)仍然被路由守卫读取,从而把用户重定向到错误页面,表现出来就是“路由无效”或白屏。

  1. Pinia 持久化没有用独立 key

多个子应用共用 sessionStorage,但你的持久化 key 没做隔离,导致:

卸载后状态残留

下次挂载被旧 store 污染

路由被旧状态拦截

第一次加载:
app → DOM 正常渲染

卸载后:
app 节点销毁了
但 router 的 DOM patch/vnode/history 仍残留

第二次加载:
Vue 尝试再次挂载到同一个节点,但旧 vnode 未清理 → 白屏
router 读取旧 token → 路由跳转失败


撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题