基于 qiankun 的应用间页面跳转

一、问题背景

在单体前端应用中,路由是单一事实源,所有页面跳转都由同一个路由实例管理。但在微前端架构中,每个微应用都是独立部署单元,拥有自己的路由实例,这导致了路由的碎片化。

微应用需要跳转到主应用或其他微应用页面,直接操作浏览器地址栏(window.location.href)会导致全页面刷新,破坏微前端的无缝体验,同时丢失应用状态。

本次应用均采用 Vue 3 + qiankun 2.7.0 架构,可采用控制反转(IoC) 模式,主应用作为依赖容器,将路由能力通过 props 注入到微应用,实现路由委托。

二、理论基础:qiankun 的依赖注入机制

  1. props 注入原理

qiankun 基于 Single-SPA 的 registerApplication API,在应用生命周期中注入 props:

dart 复制代码
// qiankun 内部简化逻辑(伪代码)
const loadApp = async (app, userProps) => {
  const lifecycle = await app.loadApp();
  return {
    mount: [async (props) => {
      // 合并默认 props 与用户自定义 props
      const mergedProps = {
        ...props,           // 容器、name 等默认属性
        ...userProps        // 用户注册的 props(mainRouter/mainNavigate)
      };
      await lifecycle.mount(mergedProps);
    }]
  };
};

这实现了依赖注入模式:主应用作为 IoC 容器,微应用被动接收依赖,而非主动创建。

  1. 控制反转在路由中的应用
模式 特征 代码表现
传统模式 微应用主动创建路由 const router = new VueRouter(); router.push()
IoC 模式 主应用创建,微应用接收 props.mainNavigate('/path')

微应用不依赖具体路由实现,只依赖抽象接口;主应用可在导航方法中统一添加拦截逻辑(权限、埋点)

scss 复制代码
┌─────────────────────────────────────┐
│         浏览器地址栏 (URL)           │ ← 用户可感知,全局共享
├─────────────────────────────────────┤
│      主应用路由层 (Main Router)      │ ← 调度中心,控制生命周期
│         ↑ 委托调用                   │
├─────────────────────────────────────┤
│   微应用业务层 (Business Logic)      │ ← 调用 props.mainNavigate
│         ↑ 触发跳转                   │
├─────────────────────────────────────┤
│   微应用组件层 (Vue Components)      │ ← 用户点击按钮
└─────────────────────────────────────┘

微应用只负责发起跳转意图,主应用负责执行路由操作,这种分层保证了架构的清晰与可控。

三、实现方案

3.1 主应用配置层

主应用作为编排中心,注册微应用时封装路由能力:

javascript 复制代码
import { registerMicroApps } from 'qiankun';
import Router from './router'; // Vue Router 实例

/**
 * 微应用注册工厂
 * 职责:统一封装路由能力,实现依赖注入
 */
const regMicroAllApp = (list) => {
  if (list.length === 0) return;

  const microAllApp = list.map((item) => ({
    name: item.name,
    entry: item.entry 
      ? item.entry 
      : window.location.origin + window.location.pathname + item.name + "/index.html",
    container: "#subapp-viewport",
    // hash 模式下的激活规则,兼容多种入口路径
    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,                // 地图 SDK(兼容沙箱外资源)
      "Sidebar": window.Sidebar,          // 侧边栏状态共享
      
      // 方案 1:封装导航方法(函数式抽象)
      "mainNavigate": (path, query = {}) => {
        // 可在此时机统一处理:权限校验、埋点、参数序列化
        console.log(`[qiankun] 导航到 ${path}`, query);
        Router.push({ path, query });
      },
      
      // 方案 2:暴露路由实例(底层能力)
      "mainRouter": Router,  // 直接传递实例,微应用可调用全部 API
    }
  }));

  registerMicroApps(microAllApp);
};

关键设计:

  • activeRule 的多种匹配规则:兼容直接访问、带 index.html、不同基础路径等场景
  • upper-name 的传递:支持微应用展示层级关系(如"监控中心 > 告警管理")
  • 全局对象(BMap、Sidebar)的共享:突破 qiankun 沙箱对 window 的隔离

3.2 微应用生命周期

微应用在 mount 生命周期中接收并存储 props:

ini 复制代码
function life(render, instance = null) {
  const obj = {};
  window.needClean = false;

  obj.bootstrap = async () => {
    console.log('[micro-app] bootstraped');
  };

  obj.mount = async (props) => {
    // 关键:将 props 挂载到全局,供业务组件访问
    window.props = props;
    
    // 监听全局状态变化(如主应用通知清理数据)
    props.onGlobalStateChange((state) => {
      window.needClean = !!state.cleanData;
    });

    // 渲染应用
    render();
  };

  obj.unmount = async () => {
    // 清理工作:销毁实例、解绑事件、清空全局变量
    window.props = null;
    instance = null;
  };

  // 暴露 qiankun 生命周期
  window.qiankunLifecycle = {
    bootstrap: obj.bootstrap,
    mount: obj.mount,
    unmount: obj.unmount
  };
}

注意事项:

  • window.props 的挂载:简单直接,但需注意内存泄漏(在 unmount 中清理)
  • 生产环境建议:使用 Vue 3 的 provide/inject 或全局状态管理,而非直接挂载 window

3.3 微应用跳转实现

业务组件中通过 window.props 获取主应用能力:

javascript 复制代码
// 微应用:AlertList.vue
const goToAlertDetail = (id) => {
  // 方案 1:使用 mainNavigate(推荐)
  if (window.props?.mainNavigate) {
    // 声明式调用,参数结构化,无需关心 URL 拼接
    window.props.mainNavigate('/alert-view', { id });
    return;
  }

  // 方案 2:直接使用 mainRouter
  if (window.props?.mainRouter) {
    // 命令式调用,需手动处理 URL 参数
    window.props.mainRouter.push(`/alert-view?id=${id}`);
    return;
  }
};

四、方案对比

维度 mainNavigate(推荐) mainRouter
封装层级 函数封装,仅暴露导航能力 直接暴露 Router 实例
参数处理 结构化对象,自动序列化 需手动拼接 URL
扩展性 主应用可统一拦截(权限、埋点) 微应用直接操作,难以管控
适用场景 大型项目,需统一管控 小型项目,快速迁移

总结:通过 qiankun 的 props 机制实现路由能力委托,解决微前端架构下应用间跳转的沙箱隔离与状态保持问题。优先使用 mainNavigate,在函数封装层统一处理横切关注点(权限、日志、降级),保持微应用与路由实现的解耦。

相关推荐
前端 贾公子2 小时前
unplugin-icons == elementPlus自动引入icon
前端·javascript·vue.js
YFLICKERH2 小时前
【Python-Web后端开发框架】Flask | Django | FastAPI | Tornado 选型与 使用 | 特性
前端·python·flask
光影少年2 小时前
说说模块化规范?CommonJS和ES Module的区别?
前端·javascript·elasticsearch
telllong2 小时前
C++20 Modules:从入门到真香
java·前端·c++20
齐鲁大虾3 小时前
如何在HTML/JavaScript中禁用Ctrl+C
前端·javascript·html
qq_406176143 小时前
深入浅出 Vue 路由:从基础到进阶全解析
前端·javascript·vue.js
陈随易3 小时前
MoonBit访谈:MoonBit开发moonclaw实现“养虾”自由
前端·后端·程序员
汀沿河3 小时前
3 LangChain 1.0 中间件(Middleware)- after_model、after_agent
前端·中间件·langchain