一、问题背景
在单体前端应用中,路由是单一事实源,所有页面跳转都由同一个路由实例管理。但在微前端架构中,每个微应用都是独立部署单元,拥有自己的路由实例,这导致了路由的碎片化。
微应用需要跳转到主应用或其他微应用页面,直接操作浏览器地址栏(window.location.href)会导致全页面刷新,破坏微前端的无缝体验,同时丢失应用状态。
本次应用均采用 Vue 3 + qiankun 2.7.0 架构,可采用控制反转(IoC) 模式,主应用作为依赖容器,将路由能力通过 props 注入到微应用,实现路由委托。
二、理论基础:qiankun 的依赖注入机制
- 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 容器,微应用被动接收依赖,而非主动创建。
- 控制反转在路由中的应用
| 模式 | 特征 | 代码表现 |
|---|---|---|
| 传统模式 | 微应用主动创建路由 | 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,在函数封装层统一处理横切关注点(权限、日志、降级),保持微应用与路由实现的解耦。