微前端路由跳转解析
一、微前端路由核心原理
在微前端架构中,路由系统承担着应用调度核心职责。qiankun 通过劫持路由事件实现应用加载控制,其核心机制包含:
- 路由监听层:通过覆写
window.history
和window.addEventListener
(popstate
/hashchange
) 捕获路由变化; - 应用匹配器:根据
activeRule
配置匹配当前路由对应的子应用; - 沙箱隔离:为每个子应用创建独立的运行环境,包括路由上下文隔离。
二、主应用跳转子应用
下面以 cra 搭建的 react 主应用以及两个子应用为例,实现微前端路由跳转,项目结构参考:
main-app/
├─ packages/
│ ├─ apps/
│ │ ├─ app1-react/ # CRA React子应用
│ │ └─ app2-vue/ # Vite Vue子应用
└─ src/ # 主应用代码
主应用跳转子应用:
// 使用 React Router 的 Link 组件
<Link to="/app1">跳转React子应用</Link>
<Link to="/app2">跳转Vue子应用</Link>
- 入口配置
- 跳转实现
main-app/src/index.js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app1-react',
entry: '//localhost:3001',
container: '#subapp-container',
activeRule: '/app1',
},
{
name: 'app2-vue',
entry: '//localhost:3002',
container: '#subapp-container',
activeRule: '/app2',
}
]);
start();
main-app/src/App.jsx
import {
BrowserRouter as Router,
Link,
Routes,
Route,
useHistory
} from 'react-router-dom';
import './App.css';
function Home() {
return (
<div>
<h1>主应用</h1>
<nav>
<Link to="/app1">跳转React子应用</Link>
<Link to="/app2">跳转Vue子应用</Link>
</nav>
</div>
);
}
function App() {
const history = useHistory();
// 暴露路由方法给子应用
window.mainAppRouter = {
push: (path) => history.push(path),
replace: (path) => history.replace(path)
};
return (
<Router basename="/">
<div className="main-app">
<nav>
<Link to="/">主应用首页</Link>
<Link to="/app1">React子应用</Link>
<Link to="/app2">Vue子应用</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
{/* 子应用容器路由 */}
<Route path="/app1/*" element={<div id="subapp-container" />} />
<Route path="/app2/*" element={<div id="subapp-container" />} />
</Routes>
</div>
</Router>
);
}
export default App;
三、子应用跳转主应用 & 子应用间跳转
子应用跳转主应用:
- 使用 history API 实现
- 通过主应用暴露的路由方法实现
// 使用 history API
window.history.pushState({}, '', '/');
// 通过主应用暴露的路由方法跳转
window.mainAppRouter.push('/');
子应用间跳转:
- 使用 history API 实现
- 通过主应用暴露的路由方法实现
// 跳转到 React 子应用
window.history.pushState({}, '', '/app1');
// 跳转到 Vue 子应用
window.history.pushState({}, '', '/app2');
// 通过主应用暴露的路由方法跳转
window.mainAppRouter.push('/app1');
window.mainAppRouter.push('/app2');
1. 子应用 app1-react 代码
- 入口配置
- 路径配置
- 跳转实现
app1-react/src/index.js
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
let root = null;
function render(props = {}) {
const { container } = props;
const rootElement = container
? container.querySelector('#root')
: document.getElementById('root');
root = ReactDOM.createRoot(rootElement);
root.render(
<BrowserRouter basename={window.__POWERED_BY_QIANKUN__ ? '/app1' : '/'}>
<App />
</BrowserRouter>
);
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 子应用生命周期
export async function bootstrap() {}
export async function mount(props) {
render(props);
}
export async function unmount(props) {
root.unmount();
}
app1-react/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
app1-react/src/App.jsx
import { BrowserRouter as Router, Link, Routes, Route, useNavigate } from 'react-router-dom';
export default function App() {
const navigate = useNavigate();
// 跳转主应用
const jumpToMain = () => window.mainAppRouter.push('/');
// 跳转Vue子应用
const jumpToVueApp = () => window.mainAppRouter.push('/app2');
return (
<Router basename={window.__POWERED_BY_QIANKUN__ ? '/app1' : '/'}>
<div className="app1">
<h2>React 子应用</h2>
<nav>
<Link to="/page1">子应用内部路由1</Link>
<Link to="/page2">子应用内部路由2</Link>
<button onClick={jumpToMain}>跳转主应用</button>
<button onClick={jumpToVueApp}>跳转Vue子应用</button>
</nav>
<Routes>
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
</Routes>
</div>
</Router>
);
}
2. 子应用 app2-vue 代码
- 入口配置
- 路由配置
- 跳转实现
app2-vue/src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
let app = null;
function render(props = {}) {
const { container } = props;
app = createApp(App);
app.use(router);
app.mount(container ? container.querySelector('#app') : '#app');
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// 微应用生命周期
export async function bootstrap() {}
export async function mount(props) {
render(props);
}
export async function unmount() {
app.unmount();
}
app2-vue/src/router.js
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{ path: '/page1', component: () => import('./views/Page1.vue') },
{ path: '/page2', component: () => import('./views/Page2.vue') }
];
const router = createRouter({
history: createWebHistory(
window.__POWERED_BY_QIANKUN__ ? '/app2' : '/'
),
routes
});
export default router;
app2-vue/src/App.vue
<template>
<div class="app2">
<h2>Vue 子应用</h2>
<router-link to="/page1">子应用内部路由1</router-link>
<router-link to="/page2">子应用内部路由2</router-link>
<button @click="jumpToMain">跳转主应用</button>
<button @click="jumpToReactApp">跳转React子应用</button>
<router-view />
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
const router = useRouter();
const jumpToMain = () => {
window.mainAppRouter.push('/');
};
const jumpToReactApp = () => {
window.mainAppRouter.push('/app1');
};
</script>
注意
- 确保所有应用使用相同的路由模式(推荐全部使用 history 模式)
- 主应用需要配置 nginx 或代理处理子应用路由的 fallback
- 子应用需要支持独立运行和嵌入运行两种模式
- 跨应用跳转建议使用主应用提供的统一路由管理
- 开发时需要同时启动主应用和子应用:
main-app: 3000
app1-react: 3001
app2-vue: 3002
三、进阶路由处理技巧
1. 路由守卫集成
// 主应用全局守卫
router.beforeEach(async (to, from, next) => {
if (to.meta.isMicroApp) {
const app = getAppConfig(to.meta.appName);
// 检查是否已加载
if (!app.loaded) {
await loadMicroApp(app);
app.loaded = true;
}
// 传递路由状态
app.instance?.setRouteState?.(to.params);
}
next();
});
2. 动态路由注入
// 异步从接口获取子应用配置
async function initDynamicRoutes() {
const apps = await fetch('/api/micro-apps');
apps.forEach(app => {
router.addRoute({
path: `/${app.code}/*`,
component: MicroAppWrapper,
meta: {
isMicroApp: true,
appConfig: app
}
});
});
}
四、常见问题解决方案
1. 路由冲突处理
// 精确匹配主应用路由
registerMicroApps([
{
activeRule: (location) =>
location.pathname.startsWith("/main-layout/app1") &&
!location.pathname.includes("/main-layout/app1/exclude"),
},
]);
2. Hash 模式适配
// 主应用 qiankun 配置
start({
sandbox: { experimentalStyleIsolation: true },
useHashMode: true, // 启用 hash 路由模式
});
// 子应用打包配置
publicPath: process.env.NODE_ENV === "production"
? "/hash-path/#/"
: "//localhost:7100/#/";
3. 异常处理策略
// 全局错误捕获
router.onError((error) => {
if (error.message.includes('chunk')) {
showReloadDialog('检测到新版本,即将刷新页面');
setTimeout(() => location.reload(), 2000);
}
});
// 子应用降级处理
window.addEventListener('unhandledrejection', (e) => {
if (e.reason.message.includes('subapp')) {
router.push('/error?type=subapp_down');
}
});