微前端路由跳转解析
一、微前端路由核心原理
在微前端架构中,路由系统承担着应用调度核心职责。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');
  }
});