跳到主要内容

最佳实践与场景

通信方案

微前端通信最简单可靠的方式:主应用 initGlobalState + 子应用通过 props 拿到的通信 API

主应用定义全局状态

import { initGlobalState } from 'qiankun';

// 定义一份"全局状态"
const actions = initGlobalState({ user: null, token: '' });

// 主应用自己也可以监听/修改
actions.onGlobalStateChange((state, prev) => {
console.log('state 变化', state);
});
actions.setGlobalState({ user: { name: 'admin' } });

子应用读写全局状态

// 子应用 mount(props) 里
export async function mount(props) {
const { onGlobalStateChange, setGlobalState } = props;

// 监听主应用状态变化(如登录态)
onGlobalStateChange((state, prev) => {
console.log('收到主应用状态', state);
});

// 向主应用上报
setGlobalState({ currentPage: '/orders' });
}

子应用不推荐自己 import 全局状态去读——通信入口统一走 mount(props) 拿到的 API,避免多实例/更新后引用错乱。

沙箱与样式隔离

start({
sandbox: {
strictStyleIsolation: true, // 开启 Shadow DOM 样式隔离(隔离最强,但子应用里弹层可能出不了容器)
experimentalStyleIsolation: true, // 备选:CSS 选择器加前缀,兼容性更好
},
});
  • JS 沙箱:默认开启(Proxy),子应用写在 window 上的全局变量在卸载后会被回收,不污染主应用和其他子应用;
  • 样式隔离:默认是「运行时作用域」(给子应用样式加前缀);strictStyleIsolation 用 Shadow DOM,隔离彻底,但要注意弹窗/抽屉渲染到 body 的场景会失效
  • 子应用里如果有挂到 body 上的弹层,配合 Shadow DOM 时容易"出不来"——用 experimentalStyleIsolation 或把弹层容器放到子应用内部更稳。

资源加载与部署

  • 主应用 start({ prefetch: 'all' }) 可预加载子应用,减少切换等待;
  • 子应用 entry 生产环境指向独立部署的静态资源地址(可放 CDN),子应用要保证自己的资源路径是绝对路径(不要用相对路径,否则切到子应用路由刷新会 404);
  • 每个子应用独立构建、独立部署,互不阻塞发版;
  • 不想给每个子应用单独起服务?可以把主应用和所有子应用都挂到同一台 nginx 下,用 URL 前缀区分 + try_files 兜底——详见部署场景:Nginx统一托管

常见坑

解法
子应用刷新 404主/子应用都用 history 路由时,子应用 base 设为自身前缀;或服务端把子应用前缀 rewrite 到子应用入口
多个子应用 chunk 冲突每个子应用 chunkLoadingGlobal / jsonpFunction 必须唯一
卸载后状态残留unmount 里销毁实例、解绑全局事件、清理定时器、清空 container 内 DOM
子应用内 document.body 全局弹层props.container 定位,或选择非 Shadow DOM 隔离
子应用间通信靠 window一律走 initGlobalState,别手写全局变量(沙箱会隔离,写也白写)

实战场景

场景 1:老系统统一入口(多技术栈并存)

需求:公司有 A 系统(jQuery 老项目)、B 系统(Vue 2)、C 系统(React),要合并进一个带统一导航的新壳子里,不改老代码

做法:

  • 新壳子做主应用,只负责顶部导航 + 路由分发;
  • 老系统各自导出生命周期钩子接入(jQuery 项目也能接:mount 里手动把现有 DOM 挪进容器即可);
  • 登录态用 initGlobalState 下发,老系统 mount(props) 里读取。

效果:老系统一行业务逻辑不改,统一入口 + 统一登录态打通。

场景 2:巨石应用渐进式拆分

需求:单体应用越来越大(几百万行、几十个路由),想拆但不敢一次性重构。

做法:

  • 主应用 = 原应用,先不动;
  • 挑一个边界清晰的路由模块(如"报表中心")拆成独立子应用,从主应用代码里删掉该模块路由,改为注册子应用;
  • 每拆一个,验证一次再拆下一个。

效果:拆一个上一个是风险可控的渐进迁移,业务不中断。

场景 3:大型中后台(共享导航 + 权限 + 全局状态)

需求:运营平台按模块分属不同团队(用户中心、订单中心、商品中心),但要共享:统一的侧边栏、登录态、权限、消息通知。

做法:

  • 主应用维护导航、路由表、权限判断,把权限结果放进全局状态;
  • 各模块子应用 mount(props)onGlobalStateChange 监听权限/登录态变化并响应;
  • 模块间跳转用主应用的路由方法(或 setGlobalState 通知主应用切路由)。

效果:各团队独立迭代,但用户视角是"一个完整的平台"。

场景 4:动态加载(插件化)

需求:后台支持"安装/卸载"功能模块(如插件市场),模块列表来自服务端,不确定有哪些。

做法:用 loadMicroApp 在运行时动态注册/卸载子应用:

import { loadMicroApp } from 'qiankun';

function mountPlugin(plugin) {
return loadMicroApp({
name: plugin.name,
entry: plugin.entry,
container: '#plugin-area',
props: { pluginId: plugin.id },
});
}

// 卸载插件时
app.unmount();

效果:新增模块只需后端配置,前端零发版。

一句话总结

qiankun 接入分三步:主应用注册 + start、子应用导出三个生命周期、子应用打包成 UMD。最佳实践集中在三件事——通信走 initGlobalState、卸载务必清理干净、每个子应用独立构建部署。按"渐进式迁移、多技术栈并存、统一入口"这几个场景去套,就能把微前端的收益吃到位,同时避开 90% 的坑。