目录

微前端尝试记:什么时候值得用,又该怎么用

微前端这个词已经火了很多年,但真要落地时,经常会遇到几个灵魂拷问:

  • 我们的项目有必要用吗?
  • 是不是换个 iframe 也能解决?
  • 上了之后会不会更复杂?

这篇文章是一次真实的尝试记录:我们为什么考虑微前端、最后怎么落地、踩过哪些坑。


1. 我们为什么会想到微前端?

当时的背景:

  • 已经有一个比较大的运营中台,使用 Vue2;
  • 新业务线希望独立迭代,想用 Vue3/Vite;
  • 不同团队节奏不一致,但最终还是要在一个「统一入口」下为业务方服务。

简单说就是:

不同团队、不同技术栈、不同发布节奏,但对外要看起来像「一个系统」。

这是典型的微前端适用场景之一。

我们也认真讨论过:

  • 方案 A:继续在原系统里加路由

    • 所有业务都塞进一个 Vue2 大仓库;
    • 优点是简单,缺点是历史包袱越来越重。
  • 方案 B:子系统独立域名

    • 完全分开,运营自己维护入口;
    • 用户在不同系统间跳转,体验割裂。

最终,我们选了一个折中方案:

用微前端把多个独立应用挂在一个统一壳子下,保证用户体验一致,同时保留各团队的技术栈和发布节奏。


2. 选型:为什么最后选了「框架型」方案

有几种路线:

  1. 自己基于 iframe + postMessage 搞一套协议;
  2. 使用成熟微前端框架(如 single-spa、qiankun、Module Federation 等);
  3. 基于构建时集成的模块联邦方案(Webpack Module Federation 等)。

我们最后选的是类似 qiankun 的「运行时加载子应用」方案,原因:

  • 接入成本相对低,不用大改原有项目;
  • 不强绑某个构建工具;
  • 社区实践比较多。

整体架构是:

主应用(Shell,负责导航/布局/统一登录)
  ├── 子应用 A(Vue2,老中台)
  ├── 子应用 B(Vue3,新模块)
  └── 子应用 C(React,小实验项目)

3. 路由接入:谁管 URL?

微前端绕不开的一个问题是:路由由谁控制?

我们的做法是:

  1. 主应用统一管理「系统级路由」

    • 比如:/platform/manager, /platform/ops, /platform/experiment 等;
    • 左侧菜单、面包屑主要由主应用渲染。
  2. 子应用管理「系统内部路由」

    • 在其挂载点下的路径由子应用负责;
    • 如:/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. 子应用之间怎么通信?

一开始我们想得太复杂,想搞「全局事件总线 + 状态中心」。
最后发现,大部分实际需求都可以整理为:

  1. 全局用户信息

    • 登录态、用户基础信息;
    • 放在主应用里,通过 props 或全局对象下发。
  2. 简单事件通知

    • 比如「某个设置修改后,让另一个子应用刷新」;
    • 用一个简易的事件总线或 message bus 即可。

我们最终实现是:

  • 主应用提供一个全局 eventBus(可简化为 mitt);
  • 子应用通过 props 拿到 eventBus
  • 约定好 event 名字即可。

示例:

// 子应用 A 里
props.eventBus.emit('config:updated');

// 子应用 B 里
props.eventBus.on('config:updated', () => {
  refreshConfig();
});

避免了过度工程化。


6. 样式与全局状态:划清边界

两个典型踩坑点:

  1. 样式污染

    • 各自的全局 CSS、reset 样式会互相影响;
    • 解决:主应用约定统一基础样式,子应用尽量使用 CSS Module / BEM / Scoped。
  2. 全局状态冲突

    • 各自用 Vuex/Redux,容易出现命名冲突;
    • 简单方式:把各自状态限定在子应用内,不搞跨应用共享全局 Store。

对于真正需要跨系统共享的东西(比如登录态),使用主应用统一管理,通过 propscontext 形式下发。


7. 什么时候值得用微前端?

根据这次实践,我总结了一句:

团队拆分 + 技术栈异构 + 独立发布诉求 较强的时候,可以认真考虑;
只是想「把几个模块分个文件夹」时,就没必要上。

适用的典型场景:

  • 多个业务线中台共用一个入口,但交给不同团队维护;
  • 存量系统技术栈较老,又想在新模块用新技术栈;
  • 希望各系统可以独立发版,降低互相影响。

不适用的情况:

  • 单一团队维护一个中小型前端项目;
  • 没有清晰的子系统边界,模块之间高度耦合。

8. 小结

微前端不是银弹,但在「多团队、多技术栈、强独立发布」的场景下,确实是一把顺手的工具。

落地时几条经验:

  1. 先想清楚要解决的具体问题,而不是为了「用上微前端」;
  2. 主应用负责导航、登录、全局样式,子应用聚焦各自业务;
  3. 通信体系不要过度设计,能用事件总线解决就先用事件总线;
  4. 提前约束好样式、依赖和全局状态的边界。

如果你正犹豫要不要上微前端,可以先用一个非核心模块做实验,踩过一轮坑之后,再决定要不要全面铺开。