Skip to content

24 · 微前端架构落地 ​

本篇解决什么问题: "该不该上微前端"是选型问题(见 A02),"上了之后怎么让它不翻车"是落地问题——本篇讲后者。 微前端的复杂度几乎全在隔离与通信这两件事的边界处理上。

📌 与 A02 的分工:A02 判断"该不该上",本篇解决"上了怎么落地"。


开篇速览 ​

关注点核心问题
方案选型qiankun / Module Federation / iframe / Web Components 怎么选
JS 沙箱子应用的 window 污染怎么办
样式隔离类名冲突、全局样式互相覆盖
应用通信怎么传数据而不产生强耦合
依赖共享React/Vue 实例必须单例,否则 Hooks 报错
路由分发主应用与子应用的路由怎么协调
预加载切换卡顿怎么缓解

一、方案选型:四种路线 ​

方案原理隔离性体验适合
iframe浏览器原生⭐⭐⭐⭐⭐ 最强❌ 差(弹窗/路由/刷新/通信都别扭)完全异构、要绝对隔离的第三方嵌入
qiankun / single-spa路由劫持 + 加载子应用 bundle + 沙箱⭐⭐⭐✅ 好多数团队的默认选择
Module Federation(Webpack 5 / Vite)构建时声明远程模块,运行时动态共享代码⭐⭐✅ 最好(无刷新、真共享)技术栈统一、需要共享组件/依赖
Web Components自定义元素 + Shadow DOM⭐⭐⭐⭐✅ 好组件级集成(见 A22)

决策速查 ​

场景选
老系统(jQuery/AngularJS)+ 新系统共存qiankun
技术栈统一(都是 React 18 / Vue3),要共享组件与依赖Module Federation
要嵌入完全不可信的第三方页面iframe
只是想在多个框架间复用一批组件Web Components(这不算微前端,别过度设计)

二、JS 沙箱:隔离 window ​

问题:两个子应用都往 window 上挂东西,或者都改 window.addEventListener,会互相干扰。

2.1 三种沙箱级别 ​

级别做法隔离性性能
快照沙箱激活时拍快照,失活时还原弱(单实例场景)中
Proxy 沙箱(qiankun 默认)用 Proxy 代理一个假的 window,子应用读写都走它✅ 强(多实例并存)好
iframe 沙箱用 iframe 的 contentWindow 作为真实隔离环境⭐⭐⭐ 最强差
js
// Proxy 沙箱的核心思路(简化)
function createSandbox() {
  const rawWindow = window
  const fakeWindow = {}
  const proxy = new Proxy(fakeWindow, {
    get(t, k) {
      if (k in t) return t[k]
      return rawWindow[k]         // 没改过 → 读真实 window
    },
    set(t, k, v) {
      t[k] = v                    // 只写到假 window,**不污染全局**
      return true
    },
  })
  return { proxy, active() {...}, inactive() {...} }
}

2.2 沙箱拦不住什么(务必知道) ​

漏网之鱼说明
全局事件监听window.addEventListener('resize') 需要在子应用卸载时主动移除
定时器setInterval 必须在卸载时清理
DOM 外的全局状态document.title、body 上的 class
样式JS 沙箱管不了 CSS(见下一节)
第三方 SDK 的副作用某些 SDK 会强行挂全局

🎯 子应用的 unmount 必须做完整清理:移除监听、清定时器、取消请求、销毁实例。 这是微前端最常见的内存泄漏来源。


三、样式隔离 ​

3.1 三种方案 ​

方案做法优点缺点
CSS Modules / 原子化构建时加哈希✅ 最彻底、零运行时要求所有子应用都采用
Shadow DOMattachShadow 真隔离✅ 原生隔离弹窗要挂到 body 就出不来;第三方组件库常不兼容
动态作用域(qiankun 的 experimentalStyleIsolation)给子应用所有选择器加前缀改动小对 @media、动态插入样式支持有限

推荐优先级:CSS Modules/原子化 > Shadow DOM > 动态前缀。

⚠️ 弹窗穿透问题:Shadow DOM 内无法让 Modal 渲染到外层 body (会脱离 Shadow 树从而丢失样式)。用 Shadow DOM 前先验证你们组件库的弹窗。

3.2 全局样式约定 ​

无论用哪种隔离,都要约定:

  • 禁止子应用写 body {}、* 这类全局选择器
  • 统一重置样式由主应用提供一次,子应用不要各引一份 reset
  • 主题 Token 由主应用注入 CSS 变量(见 A14)

四、应用通信:保持松耦合 ​

4.1 三种方式(按推荐度) ​

① Props / 生命周期参数(首选)

js
// 主应用注册子应用时传入
registerMicroApps([{
  name: 'user',
  entry: '//localhost:3001',
  container: '#subapp',
  activeRule: '/user',
  props: {
    token: () => store.token,          // ✅ 传**getter**,保证拿到最新值
    onLogout: () => { /* 回调 */ },
    routerBase: '/user',
  },
}])

② 全局状态(props + initGlobalState)

js
// qiankun
const actions = initGlobalState({ user: null })
actions.onGlobalStateChange((state, prev) => { /* 子应用监听 */ })
actions.setGlobalState({ user })      // 主应用修改

③ URL / 自定义事件(最松耦合)

js
// 主子应用通过 URL 参数或 CustomEvent 通信 → 完全无直接依赖
window.dispatchEvent(new CustomEvent('cart:change', { detail }))

4.2 通信三原则 ​

原则说明
主 → 子 传 getter,不传快照值传值会拿到旧数据;传函数才能实时
子 → 主 用回调/事件,不直接调主的方法避免子应用编译期依赖主应用
共享状态尽量少传得越多,耦合越紧,改造越难

🎯 跨应用状态共享可以用框架无关的 Zustand(见 A07), 但要放在主应用创建、通过 props 下发,子应用不要自己 new 一个。


五、依赖共享:React/Vue 必须单例 ​

问题:子应用各打包一份 React,会出现:

症状原因
"Invalid hook call"存在多个 React 实例,Hooks 的规则检查失败
组件库样式/行为异常多份实例导致 Context 不共享
体积暴涨每个子应用都打包 React/Vue

5.1 Module Federation 的共享配置(推荐) ​

js
// webpack.config.js(主应用与子应用都要配)
new ModuleFederationPlugin({
  name: 'host',
  remotes: { user: 'user@//localhost:3001/remoteEntry.js' },
  shared: {
    react:       { singleton: true, requiredVersion: '^18.0.0' },  // ✅ singleton 保证单例
    'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
  },
})

5.2 qiankun 场景的做法 ​

把 React/Vue 配成 external,由主应用通过 importmap 或 CDN 提供:

html
<script type="importmap">
{ "imports": { "react": "https://cdn/esm.sh/react@18", "react-dom": "https://cdn/esm.sh/react-dom@18" } }
</script>

⚠️ 版本必须一致:主应用 React 18、子应用 React 17,共享后会崩。 统一基础框架版本是微前端的前提,否则别共享。


六、路由分发 ​

主应用负责一级路由,子应用负责二级路由。

js
// 主应用(history 模式)
registerMicroApps([{
  name: 'user',
  activeRule: '/user',           // 主应用匹配到 /user 就激活子应用
  props: { routerBase: '/user' } // ✅ 把 base 传给子应用
}])

// 子应用路由要设置 base
createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/user' : '/')

要点:

要点说明
base 必须传子应用独立运行与嵌入运行用不同 base
404 兜底主应用要处理"子应用路由未匹配"的情况
子应用间跳转用主应用的 router(history.pushState),不要子应用自己跳

七、预加载与性能 ​

js
// qiankun:首屏后预加载其他子应用的静态资源
start({ prefetch: 'all' })          // 或 'all' | true
// 更精细:手动预加载
prefetchApps([{ name: 'user', entry: '//localhost:3001' }])

权衡:

策略说明
prefetch: 'all'首屏后加载全部,切换快但浪费流量
手动 prefetchApps只对高概率访问的子应用预加载(推荐)
不预加载首次切换有白屏

其他性能要点:

  • 子应用公共依赖共享(见第五节)→ 体积显著下降
  • 子应用 mount 时避免重型初始化
  • 卸载时必须销毁,否则内存只增不减

自检清单 ​

  • [ ] 能按场景在 iframe / qiankun / MF / Web Components 之间选型
  • [ ] 能说清 Proxy 沙箱的原理,以及它拦不住什么(事件、定时器、样式)
  • [ ] 能说出样式隔离的三种方案与优先级,以及 Shadow DOM 的弹窗问题
  • [ ] 知道 React/Vue 必须单例共享,以及 singleton: true 的配置
  • [ ] 能说出子应用 unmount 必须清理哪些东西

常见误区 ​

❌ 误区✅ 事实
"上了微前端,子应用就完全隔离了"JS 沙箱管不了样式、全局事件、定时器
"iframe 最安全所以最好"体验差(弹窗/路由/通信),只适合第三方嵌入
"Shadow DOM 能隔离一切"弹窗挂到 body 会丢失样式
"子应用各装各的 React 没问题"会 Invalid hook call,且体积翻倍
"微前端能解决所有耦合问题"它解决部署与团队解耦,不解决业务耦合

动手练习 ​

  1. 用 qiankun 搭一个"主应用 + 两个子应用"的最小骨架,验证 JS 沙箱(子应用挂 window.foo,主应用读不到)。
  2. 故意在子应用里 setInterval 且不清理,切换几次后观察内存与控制台(体会泄漏),再补上 unmount 清理。
  3. 用 Module Federation 把 React 配成 singleton: true 共享,对比共享前后的产物体积。

相关文档 ​