Qiankun 生命周期与数据通信实战

微前端是一种前端架构模式 ,它将一个大型的前端应用拆分成多个独立、可独立部署的子应用 ,然后通过一个主应用(或基座应用)将这些子应用集成在一起。

项目支持作为子应用集成到 qiankun 微前端框架中,qiankun 是一个基于 single-spa 的微前端框架,主要特点包括:基于路由的自动激活、样式隔离、JS 沙箱、预加载、全局状态管理。

主应用代码分析

主应用与子应用交互流程如下:

1)注册阶段 :主应用调用 regMicroAllApp(list) 批量注册子应用

2)路由生成 :调用 getMicroAllRouter(microAll) 生成微应用路由

3)启动框架 :调用 loadingMicro() 启动 qiankun 框架

4)路由匹配 :当 URL 匹配到子应用的 activeRule 时

5)子应用挂载 :触发子应用的 mount 生命周期,调用 render(props) 渲染

6)状态同步 :通过 actions 同步主应用与子应用的状态

7)子应用卸载 :当离开子应用路由时,触发 unmount 生命周期

相关逻辑封装到 main\spa\node_modules@pangu\mixmicro\dist\index.esm.js 里

子应用注册机制

微前端路由采用双层路由体系:主应用配置子应用的入口路由和激活规则 ,控制子应用是否加载;子应用配置内部页面的具体路由 ,控制内部页面显示

regMicroAllApp(list) - 批量注册子应用

框架会记录每个子应用的 name 、 entry 、 activeRule 等信息,监听 URL 变化,当 URL 匹配到某个子应用的 activeRule 时,就会准备加载该子应用

  • 参数 list:包含所有子应用配置的数组

  • entry:子应用入口,支持自定义或默认路径( origin/pathname/name/index.html )

  • container:子应用挂载容器,固定为 #subapp-viewport

  • activeRule:子应用激活规则,支持多种 URL 格式

  • props:传递给子应用的数据,包括上层产品名、百度地图 API、侧边栏和主应用路由

    const regMicroAllApp = (list) => {
    if (regMicroAllApp.length === 0)
    return;
    const microAllApp = [];
    list.map((item) => {
    microAllApp.push({
    name: item.name,
    entry: item.entry ? item.entry : window.location.origin + window.location.pathname + item.name + "/index.html",
    container: "#subapp-viewport",
    activeRule: ["/#/" + item.name, "/index.html#/" + item.name, window.location.pathname + "#/" + item.name, window.location.pathname + "index.html#/" + item.name],
    props: {
    "upper-name": item["upper-name"],
    "BMap": window.BMap,
    "Sidebar": window.Sidebar,
    "mainRouter": Router
    }
    });
    });
    registerMicroApps(microAllApp);
    };

getMicroAllRouter(microAll) - 生成微应用路由

为每个子应用生成两个动态路由,使用 Vue Router 的 /:pathMatch(.) 和 /:pathMatch(.)* 匹配所有子应用路由,路由组件统一使用 @pangu/layout/index.vue

复制代码
const getMicroAllRouter = (microAll) => {
  const microAllRouter = [];
  microAll.map((item) => {
    microAllRouter.push({
      name: item.name,
      path: "/" + item.name + "/:pathMatch(.*)",
      meta: { parameter: true },
      component: () => import("@pangu/layout/index.vue")
    });
    microAllRouter.push({
      name: item.name,
      path: "/" + item.name + "/:pathMatch(.*)*",
      meta: { parameter: true },
      component: () => import("@pangu/layout/index.vue")
    });
  });
  return microAllRouter;
};

当 URL 匹配到子应用的 activeRule 时,框架会:

1)加载子应用资源 :通过 entry 地址获取子应用的 HTML、CSS 和 JavaScript

2)创建挂载容器 :在主应用的 #subapp-viewport 容器中为子应用创建 DOM 节点

3)执行子应用代码 :调用子应用的 mount 生命周期函数

4)渲染子应用 :子应用在自己的容器中渲染内容

子应用生命周期管理

  • bootstrap:子应用初始化,目前为空实现

  • mount:子应用挂载,保存 props 到全局 window 对象,设置应用为嵌入模式,配置路由前置守卫,同步路由元信息到全局状态,并调用 render 函数挂载子应用

  • unmount :子应用卸载,检查是否需要清理数据(通过 microNeedClean 标识),若需要清理,调用 setting/exit 退出并卸载实例,否则仅卸载实例

  • update :子应用更新,目前为空实现

    function life(render, instance = null) {
    const obj = {};
    obj.bootstrap = async () => {
    };
    obj.update = async () => {
    };
    obj.mount = async (props) => {
    window.props = props;
    console.log("++++微应用渲染++++");
    Store.commit("setting/SET_EMBEDDING", true);
    if (Router) {
    Router.beforeEach((to, from, next) => {
    props.setGlobalState && props.setGlobalState({
    meta: to.meta,
    cleanData: false
    });
    next();
    });
    }
    instance = render(props);
    };
    obj.unmount = async (props) => {
    const microNeedClean = window.localStorage.getItem("microNeedClean") === "true";
    if (microNeedClean) {
    localStorage.removeItem("microNeedClean");
    Store.dispatch("setting/exit");
    instance.unmount();
    } else {
    instance.unmount();
    }
    };
    window.qiankunLifecycle = {
    bootstrap: obj.bootstrap,
    mount: obj.mount,
    unmount: obj.unmount,
    update: obj.update
    };
    }

数据通信

props 传递

在注册子应用时,通过 props 传递静态数据:

复制代码
// 主应用注册时传递
props: {
  "upper-name": item["upper-name"], // 上层应用名
  "BMap": window.BMap, // 百度地图API
  "Sidebar": window.Sidebar, // 侧边栏组件
  "mainRouter": Router // 主应用路由实例
}

// 子应用中使用
console.log(window.props["upper-name"]); // 输出: "mainapp"

全局状态管理

通过 actions 对象实现主应用和子应用之间的动态数据共享:

  • 初始化全局状态 initialState ,包含元信息、皮肤、语言等

  • actions.onGlobalStateChange:监听全局状态变化,同步到 Vuex 的 micro 模块

  • actions.setGlobalStatePg:封装了全局状态设置方法

    const initialState = {
    meta: null,
    cleanData: false,
    skin: "",
    lang: "",
    mobileCloudData: null,
    expandData: null,
    thcloud: null
    };
    const actions = initGlobalState(initialState);
    actions.onGlobalStateChange((state, prev) => {
    if (state.meta) {
    state.meta.parent_id = getParentId();
    }
    Store.commit("micro/SET_PARAMETER", state);
    });
    actions.setGlobalStatePg = (val) => {
    actions.setGlobalState({ ...initialState, ...val });
    };

微前端框架启动

使用 window.qiankunStarted 避免重复启动,调用 qiankun 的 start 方法启动框架,配置沙箱模式: strictStyleIsolation: false (不严格隔离样式)

复制代码
function loadingMicro() {
  if (!window.qiankunStarted) {
    window.qiankunStarted = true;
    console.log("++++非容器内启动乾坤,加载路由会自动加载微应用");
    setTimeout(() => {
      start({
        sandbox: { strictStyleIsolation: false }
      });
    }, 0);
  }
}

子应用代码分析

代码目录

复制代码
- .vscode/               # VS Code配置文件
- build/                 # 构建脚本
- dev/                   # 开发环境配置
- http-server/           # HTTP服务器配置(Go语言实现)
- public/                # 静态资源目录
- src/                   # 源代码目录
  - app/                  # 应用入口和全局配置
    - router/              # 路由配置
      - access.ts          # 路由权限控制
      - index.ts           # 路由实例
      - routes-stack.ts    # 路由栈管理
      - routes.ts          # 路由定义
    - store/               # Vuex状态管理
      - index.ts           # Store实例
    - App.vue              # 根组件
    - index.ts             # 应用初始化
  - assets/               # 静态资源(图片、图标、样式等)
  - common/               # 公共组件、工具和服务
  - modules/              # 业务功能模块
  - main.ts               # 应用入口文件
  - app-config.d.ts       # 应用配置类型定义
- .browserslistrc        # 浏览器兼容性配置
- .cz.yaml               # Commitizen配置
- .editorconfig          # 编辑器配置
- .env                   # 环境变量配置
- .eslintignore          # ESLint忽略配置
- .eslintrc.js           # ESLint配置
- .gitignore             # Git忽略配置
- .npmrc                 # npm配置
- .prettierrc            # Prettier配置
- Dockerfile             # Docker配置
- README.md              # 项目说明文档
- babel.config.js        # Babel配置
- config.js              # 项目配置
- package.json           # 项目依赖和脚本

入口文件 main.ts

作为应用程序的入口文件,负责微前端环境检测、生命周期函数定义和全局配置

环境检测

检测当前是否运行在 qiankun 微前端环境中,如果是微前端环境,动态设置 webpack 的 publicPath,确保资源正确加载

  • POWERED_BY_QIANKUN:qiankun 注入的全局变量,用于标识微前端环境

  • INJECTED_PUBLIC_PATH_BY_QIANKUN:qiankun 注入的资源基础路径,解决子应用资源加载路径问题

    if ((window as any).POWERED_BY_QIANKUN) {
    webpack_public_path = (window as any).INJECTED_PUBLIC_PATH_BY_QIANKUN;
    }

生命周期函数

  • bootstrap:初始化函数,只会在子应用第一次启动时调用一次

  • mount:挂载函数,当子应用被挂载到主应用时调用

  • unmount:卸载函数,当子应用从主应用中卸载时调用,负责清理资源

  • props:主应用传递给子应用的数据和方法

    export async function bootstrap() {
    //console.log('VueMicroApp bootstraped');
    }

    export async function mount(props: any) {
    (window as any).QIANKUN_PROPS = props;
    storeTest(props);
    render(props);
    }

    export async function unmount() {
    instance.destroy(); instance.el.innerHTML = '';
    instance = null;
    router = null;
    }

全局状态变化

通过 onGlobalStateChange 监听主应用传递的全局状态变化,实现主应用与子应用之间的状态同步,如语言切换、主题切换等

  • 参数: state 是当前全局状态, prev 是上一次的状态

    function storeTest(props: any) {
    props.onGlobalStateChange &&
    props.onGlobalStateChange((state: any, prev: any) => {
    console.log([onGlobalStateChange - ${props.name}]:, state);

    复制代码
        state?.lang && switchLang(state.lang);
        state?.skin && setTheme(state.skin);
      }, true);

    }

应用路径工具 getAppPath.ts

处理应用程序在不同环境(独立运行/微前端)下的路径逻辑,确保 API 路由和资源定位的正确性

  • 微前端环境逻辑:优先从 qiankun props 中获取 upper-name 属性;如果没有,从 URL 路径中提取第一个路径段

  • 独立运行环境 :使用环境变量 VUE_APP_PRODUCT 的值

    export function getAppPath(): string {
    let pathName;
    if ((window as any).POWERED_BY_QIANKUN) {
    if ('props' in window && (window as any).QIANKUN_PROPS['upper-name']) {
    pathName = (window as any).QIANKUN_PROPS['upper-name'];
    } else {
    pathName = window.location.pathname.split('/')[1];
    }
    } else {
    pathName = process.env.VUE_APP_PRODUCT;
    }
    global.AppPath_PRODUCT = pathName;
    return pathName;
    }

应用启动文件 index.ts

实现应用程序的启动和模块加载逻辑,动态导入子模块并集成路由和状态管理

动态导入子模块

从环境变量 VUE_APP_CONFIG 中解析模块配置,使用 webpack 的动态导入功能按需加载模块:

  • 如果模块包含路由配置,调用 addRoute 添加到路由系统

  • 如果模块包含状态管理,调用 addModule 添加到 Vuex store

    async function loadModules() {
    const config = process.env.VUE_APP_CONFIG;
    if (!config) {
    throw Error('No config found');
    }
    const conf = JSON.parse(JSON.parse(config)) as AppConfig;
    const modules = conf.modules;

    复制代码
    const modulesPromise = modules.map(
      (name) =>
        new Promise((resolve) => {
          import(
            /*
              webpackInclude: INCLUDED_MODULES,
              webpackMode: 'lazy-once',
              webpackChunkName: 'modules',
            */
            '@/modules/' + name + '/module.ts'
          ).then((result) => {
            const mod = result.default as AppModule;
            if (mod.router) {
              const r = mod.router;
              addRoute(r.name, r.path, r.routes);
            }
            if (mod.store) {
              const s = mod.store;
              addModule(s.name, s.module);
            }
            resolve(mod);
          });
        }),
    );
    await Promise.all(modulesPromise);
    
    return conf;

    }

路由、状态管理

  • 路由守卫 :添加全局路由守卫,实现权限控制和状态同步

  • 状态同步 :通过 props.setGlobalState 将子应用状态同步给主应用

  • Vue 实例创建 :根据是否有容器参数决定挂载方式,支持独立运行和微前端挂载

    export const start = async (selector: string, props: any) => {
    const [, conf] = await Promise.all([loadData(), loadModules()]);
    addRouteGuard();

    复制代码
    // 路由守卫,与主应用通信
    if (router) {
      router.beforeEach((to: any, from: any, next: any) => {
        if (props.setGlobalState) {
          props.setGlobalState({
            ignore: props.name,
            user: {
              name: props.name,
            },
            meta: to.meta,
          });
        }
        
        // 权限验证
        const token =
          sessionStorage.getItem('Authorization') || sessionStorage.getItem('ueba-Authorization');
    
        if (!token) {
          window.location.href = '#/404';
        } else {
          next();
        }
      });
    }
    
    // 初始化Vue实例
    const instance = new Vue({
      router,
      store,
      i18n,
      render: (h) => h(App, { props: { appConfig: conf } }),
    }).$mount(props.container ? props.container.querySelector(selector) : selector);
    return { instance, router };

    };

开发环境代理配置 proxy.js

该文件是开发环境的 API 代理配置 ,用于在独立开发子应用时,将 API 请求转发到对应的后端服务

为不同的 API 路径配置不同的代理目标,自动添加认证信息和用户身份头,处理跨域和 SSL 证书问题

复制代码
const generateProxyConfig = (config) => {
  return {
    target: config.target,               // 代理目标地址
    pathRewrite: config.pathRewrite || {},  // 路径重写规则
    auth: 'admin:secret+3s',             // 代理认证信息
    changeOrigin: true,                  // 启用跨域
    ssl: {
      rejectUnauthorized: false,         // 不验证SSL证书
      requestCert: false,
    },
    onProxyReq: (proxyReq) => {
      // 添加请求头,用于模拟用户身份
      proxyReq.setHeader('userId', '101');
      proxyReq.setHeader('userName', '666666666');
      // ...其他请求头
    },
    onProxyRes: (proxyRes, req, res) => {
      // 记录代理响应日志
      res.on('finish', () => {
        const code = res.statusCode;
        // 根据状态码打印不同颜色的日志
        // ...日志逻辑
      });
    },
  };
};

const proxyConfigs = {
  '/m1/1961322-1395971-default': generateProxyConfig({ target: 'http://127.0.0.1:4523' }),
  '/ueba/api': generateProxyConfig({ target: 'http://192.168.77.17:8087' }),
  '/model-manager/api': generateProxyConfig({ target: 'http://192.168.77.4:8088' }),
  '/csa': generateProxyConfig({ target: 'https://192.168.97.206:8443' }),
};

完整互动流程示例

场景 :用户登录主应用后,点击"数据分析"菜单进入数据子应用,查看图表后返回主应用。

1)注册阶段 :

主应用调用 regMicroAllApp 注册包括 "data-analysis" 在内的所有子应用,同时调用 getMicroAllRouter 生成子应用路由

2)用户访问主应用 :

主应用正常渲染,用户登录成功后,主应用通过 actions.setGlobalStatePg 设置全局用户信息

3)用户点击"数据分析"菜单 :

主应用路由跳转到 /data-analysis,框架检测到 URL 匹配 data-analysis 子应用的 activeRule

4)子应用加载 :

框架通过子应用的 entry 地址加载 HTML、CSS 和 JS,调用子应用的 bootstrap、mount 生命周期,并传递 props,子应用渲染数据分析界面

5)子应用内部操作 :

用户在子应用中切换图表类型,子应用通过 actions.setGlobalState 更新全局状态,主应用通过 onGlobalStateChange 监听到状态变化

6)返回主应用 :

用户点击主应用的"首页"菜单,主应用路由跳转到 /home,框架检测到 URL 不再匹配子应用的 activeRule,调用子应用的 unmount 生命周期,卸载子应用

相关推荐
LawrenceLan2 小时前
Flutter 零基础入门(十五):继承、多态与面向对象三大特性
开发语言·前端·flutter·dart
二川bro2 小时前
详细解析 cesiumViewer.render() 和 requestAnimationFrame(render)
前端
前端付豪2 小时前
必知Node应用性能提升及API test 接口测试
前端·react.js·node.js
王同学 学出来2 小时前
vue+nodejs项目在服务器实现docker部署
服务器·前端·vue.js·docker·node.js
一道雷2 小时前
让 Vant 弹出层适配 Uniapp Webview 返回键
前端·vue.js·前端框架
bug总结2 小时前
uniapp+动态设置顶部导航栏使用详解
java·前端·javascript
晴殇i2 小时前
深入理解MessageChannel:JS双向通信的高效解决方案
前端·javascript·程序员
毕设十刻2 小时前
基于Vue的民宿管理系统st4rf(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
kkkAloha2 小时前
倒计时 | setInterval
前端·javascript·vue.js