最佳实践与场景
通信方案
微前端通信最简单可靠的方式:主应用 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% 的坑。