微前端架构实战:从入门到落地的完整指南
微前端别乱搞,先想清楚这几点
我第一次上手微前端的时候,以为就是把几个子应用拼在一起,主应用搭个壳子就行。结果上线后三天两头出问题:样式冲突、内存泄漏、路由跳转错乱……折腾得我差点想回老家种地。后来踩了足够多的坑,才慢慢摸索出一套相对靠谱的写法。今天就聊聊我现在的做法,以及那些千万别碰的雷区。
我的写法,亲测靠谱
我现在用的是 qiankun,不是因为它最牛,而是社区生态成熟,文档虽然糙但能用。关键是我自己封装了一层加载逻辑,避免直接裸调 registerMicroApps。下面是我现在主应用里加载子应用的核心代码:
// main.js
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'app1',
entry: '//localhost:8081',
container: '#subapp-viewport',
activeRule: '/app1',
props: {
globalStore: window.__GLOBAL_STORE__,
onGlobalStateChange: (state) => {
// 主应用状态变更通知子应用
console.log('子应用收到全局状态更新:', state);
}
}
},
{
name: 'app2',
entry: '//localhost:8082',
container: '#subapp-viewport',
activeRule: '/app2',
props: {
globalStore: window.__GLOBAL_STORE__,
onGlobalStateChange: (state) => {
console.log('子应用收到全局状态更新:', state);
}
}
}
];
// 关键:加一层错误兜底
registerMicroApps(
apps,
{
beforeLoad: async (app) => {
console.log('准备加载子应用:', app.name);
},
afterMount: (app) => {
console.log('子应用挂载完成:', app.name);
},
// 最重要:子应用加载失败时不要让整个页面白屏
error: (error, app) => {
console.error(子应用 ${app.name} 加载失败:, error);
// 可以在这里展示 fallback UI
document.getElementById('subapp-viewport').innerHTML = '<div>服务暂时不可用,请稍后再试</div>';
}
}
);
start({
prefetch: true, // 提前加载资源,但注意别在低配手机上开,会卡
sandbox: {
strictStyleIsolation: false, // 别开 strict,除非你确定子应用没用 CSS-in-JS
experimentalStyleIsolation: true // 这个够用,靠属性隔离
}
});
为什么这么写?几个点:
- props 透传全局状态:别用 qiankun 自带的 globalState,太弱。我直接在 window 上挂一个轻量 store(比如用 tiny-emitter 实现),主子应用都能读写,通信更灵活。
- error 回调必须处理:子应用挂了不能让主应用也瘫痪。我见过太多团队上线后因为一个子应用 502 导致整个系统不可用。
- 别开 strictStyleIsolation:这个模式会用 Shadow DOM,但很多 UI 库(比如 Element Plus 的弹窗)会挂到 body 下,导致样式丢失。experimentalStyleIsolation 用 data 属性隔离,兼容性好得多。
这几种错误写法,别再踩坑了
以下是我和同事踩过的经典反面案例,新手特别容易中招:
1. 子应用用绝对路径加载静态资源
比如子应用里写:<img src="/assets/logo.png">。在独立运行时没问题,但被主应用加载后,路径会变成主应用的域名,图片 404。正确做法是:
- 开发时用相对路径:
./assets/logo.png - 或者构建时注入 publicPath(webpack 配置):
// webpack.config.js (子应用) output: { publicPath: process.env.NODE_ENV === 'production' ? '//your-subapp-domain.com/' : '/' }
2. 路由用 hash 模式硬切
有些团队为了省事,主应用用 history,子应用用 hash,然后靠 window.location.hash = '#/xxx' 跳转。结果用户刷新页面,子应用路由全丢。微前端里所有应用必须用同一种路由模式,而且最好都用 history。主应用负责拦截所有路由,子应用只响应自己的 activeRule 区间。
3. 全局事件监听不清理
子应用里写了 addEventListener('resize', handler),但没在 unmount 时移除。结果每次切换子应用,内存里多一个监听器,页面越用越卡。qiankun 的子应用必须导出 unload 方法:
// 子应用入口
export async function bootstrap() { /* ... */ }
export async function mount(props) {
// 绑定事件
window.addEventListener('resize', myHandler);
}
export async function unmount() {
// 必须清理!
window.removeEventListener('resize', myHandler);
}
如果你用 React,可以在 useEffect 里返回清理函数,但要确保这个 effect 在 unmount 时执行。Vue 的话,在 beforeUnmount 里处理。
实际项目中的坑
除了代码层面,部署和协作也有不少坑:
本地开发联调巨麻烦:主应用跑 3000,子应用跑 8081/8082,每次改子应用都要重启主应用?太折磨了。我的方案是:
- 主应用加个 dev-proxy,把 /app1 代理到 localhost:8081
- 子应用独立开发时,mock 主应用传来的 props(比如 globalStore)
这样主应用不用重启,子应用改完自动热更新。
公共依赖别重复打包:主应用和子应用都用了 lodash,结果 bundle 里有两份。解决方法是在 webpack 里 external 掉公共库,或者用 module federation 共享依赖。但要注意版本必须严格一致,否则运行时出错。
登录态怎么共享?别每个子应用单独存 token。我们统一用主应用管理 auth,子应用通过 props 拿 token,或者约定从 localStorage 的固定 key 读取。但要注意安全,别把敏感信息放 localStorage。
还有个头疼的问题:子应用的 API 地址。别在子应用里硬编码 https://jztheme.com/api,应该由主应用通过 props 传入,或者子应用读取环境变量。这样测试环境、生产环境切换才方便。
最后说两句
微前端不是银弹。如果你的团队只有三五个人,业务也不复杂,硬上微前端只会增加维护成本。它适合大型团队、多个独立产品线、技术栈异构的场景。上了微前端后,一定要有统一的规范:目录结构、构建配置、通信方式、错误监控……否则后期根本没法维护。
以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流,比如你们怎么处理子应用之间的跳转?或者怎么优化首屏加载速度?这些我还在摸索中。

暂无评论