微前端尝试记:什么时候值得用,又该怎么用
目录
微前端这个词已经火了很多年,但真要落地时,经常会遇到几个灵魂拷问:
- 我们的项目有必要用吗?
- 是不是换个 iframe 也能解决?
- 上了之后会不会更复杂?
这篇文章是一次真实的尝试记录:我们为什么考虑微前端、最后怎么落地、踩过哪些坑。
1. 我们为什么会想到微前端?
当时的背景:
- 已经有一个比较大的运营中台,使用 Vue2;
- 新业务线希望独立迭代,想用 Vue3/Vite;
- 不同团队节奏不一致,但最终还是要在一个「统一入口」下为业务方服务。
简单说就是:
不同团队、不同技术栈、不同发布节奏,但对外要看起来像「一个系统」。
这是典型的微前端适用场景之一。
我们也认真讨论过:
-
方案 A:继续在原系统里加路由
- 所有业务都塞进一个 Vue2 大仓库;
- 优点是简单,缺点是历史包袱越来越重。
-
方案 B:子系统独立域名
- 完全分开,运营自己维护入口;
- 用户在不同系统间跳转,体验割裂。
最终,我们选了一个折中方案:
用微前端把多个独立应用挂在一个统一壳子下,保证用户体验一致,同时保留各团队的技术栈和发布节奏。
2. 选型:为什么最后选了「框架型」方案
有几种路线:
- 自己基于 iframe + postMessage 搞一套协议;
- 使用成熟微前端框架(如 single-spa、qiankun、Module Federation 等);
- 基于构建时集成的模块联邦方案(Webpack Module Federation 等)。
我们最后选的是类似 qiankun 的「运行时加载子应用」方案,原因:
- 接入成本相对低,不用大改原有项目;
- 不强绑某个构建工具;
- 社区实践比较多。
整体架构是:
主应用(Shell,负责导航/布局/统一登录)
├── 子应用 A(Vue2,老中台)
├── 子应用 B(Vue3,新模块)
└── 子应用 C(React,小实验项目)
3. 路由接入:谁管 URL?
微前端绕不开的一个问题是:路由由谁控制?
我们的做法是:
-
主应用统一管理「系统级路由」
- 比如:
/platform/manager,/platform/ops,/platform/experiment等; - 左侧菜单、面包屑主要由主应用渲染。
- 比如:
-
子应用管理「系统内部路由」
- 在其挂载点下的路径由子应用负责;
- 如:
/platform/manager/#/user/list或基于history的子路由。
示例:
- 主应用路由:
/platform/manager→ 加载子应用 A; - 子应用 A 内部:
/platform/manager/#/user/list、/platform/manager/#/order/detail。
优点:
- 主应用只关心「挂哪个子应用」;
- 子应用按自己熟悉的方式管理内部路由。
4. 子应用接入:生命周期与资源加载
以 qiankun 风格为例,子应用需要暴露几个生命周期函数:
export async function bootstrap() {
// 初始化,只执行一次
}
export async function mount(props) {
// 挂载到 DOM
const { container } = props;
app = createApp(App);
app.mount(container ? container.querySelector('#app')! : '#app');
}
export async function unmount() {
// 卸载
app.unmount();
}
主应用负责:
- 根据路由匹配加载子应用的入口 JS/CSS;
- 调用对应的
mount/unmount。
为了减少加载延迟,我们对高频子应用做了「预加载」:
- 用户登录后,后台静默加载其入口资源;
- 真正点击菜单进入时,能更快看到首屏。
5. 子应用之间怎么通信?
一开始我们想得太复杂,想搞「全局事件总线 + 状态中心」。
最后发现,大部分实际需求都可以整理为:
-
全局用户信息
- 登录态、用户基础信息;
- 放在主应用里,通过
props或全局对象下发。
-
简单事件通知
- 比如「某个设置修改后,让另一个子应用刷新」;
- 用一个简易的事件总线或 message bus 即可。
我们最终实现是:
- 主应用提供一个全局
eventBus(可简化为 mitt); - 子应用通过
props拿到eventBus; - 约定好 event 名字即可。
示例:
// 子应用 A 里
props.eventBus.emit('config:updated');
// 子应用 B 里
props.eventBus.on('config:updated', () => {
refreshConfig();
});
避免了过度工程化。
6. 样式与全局状态:划清边界
两个典型踩坑点:
-
样式污染
- 各自的全局 CSS、reset 样式会互相影响;
- 解决:主应用约定统一基础样式,子应用尽量使用 CSS Module / BEM / Scoped。
-
全局状态冲突
- 各自用 Vuex/Redux,容易出现命名冲突;
- 简单方式:把各自状态限定在子应用内,不搞跨应用共享全局 Store。
对于真正需要跨系统共享的东西(比如登录态),使用主应用统一管理,通过 props 或 context 形式下发。
7. 什么时候值得用微前端?
根据这次实践,我总结了一句:
团队拆分 + 技术栈异构 + 独立发布诉求 较强的时候,可以认真考虑;
只是想「把几个模块分个文件夹」时,就没必要上。
适用的典型场景:
- 多个业务线中台共用一个入口,但交给不同团队维护;
- 存量系统技术栈较老,又想在新模块用新技术栈;
- 希望各系统可以独立发版,降低互相影响。
不适用的情况:
- 单一团队维护一个中小型前端项目;
- 没有清晰的子系统边界,模块之间高度耦合。
8. 小结
微前端不是银弹,但在「多团队、多技术栈、强独立发布」的场景下,确实是一把顺手的工具。
落地时几条经验:
- 先想清楚要解决的具体问题,而不是为了「用上微前端」;
- 主应用负责导航、登录、全局样式,子应用聚焦各自业务;
- 通信体系不要过度设计,能用事件总线解决就先用事件总线;
- 提前约束好样式、依赖和全局状态的边界。
如果你正犹豫要不要上微前端,可以先用一个非核心模块做实验,踩过一轮坑之后,再决定要不要全面铺开。