一、微前端概述
1.1 什么是微前端?
微前端(Micro-Frontends)是一种类似于微服务的架构风格,将单个前端应用拆分为多个可以独立开发、独立部署的小型应用,最终组合成一个完整的前端应用。
核心思想:主应用负责路由分发、样式隔离、生命周期管理,子应用各自独立运行。
1.2 为什么需要微前端?
| 痛点 | 说明 |
|---|---|
| 技术栈耦合 | 老项目无法升级新框架,新项目无法替换老技术栈 |
| 团队规模大 | 单一代码库难以支撑多个团队并行开发 |
| 部署效率低 | 修改一行代码需要重新构建整个应用 |
| 模块间依赖复杂 | 一个大应用拆分成多个子应用,独立部署 |
1.3 微前端的优缺点
优点:
- 技术栈无关,子应用可以使用 React/Vue/Angular 等不同框架
- 独立开发、独立部署
- 渐进式升级,老项目可逐步迁移
缺点:
- 复杂度增加,调试困难
- 样式隔离存在边界情况
- 子应用间通信机制复杂
- 首屏加载可能变慢(多个 JS 加载)
二、qiankun 核心概念
2.1 qiankun 是什么?
qiankun 是蚂蚁金服开源的微前端框架,基于 single-spa 改造,是目前国内最流行的微前端方案之一。
核心特性:
- 基于 single-spa 封装,使用更简单
- 技术栈无关,子应用无感知
- 样式隔离(CSS Scoped)
- JS 沙箱(ProxySandbox / LegacySandbox)
- 应用间通信
- 预加载策略
2.2 架构组成
+--------------------------------------------------+
| 主应用 (Host) |
| +-------------+ +-------------+ +-------------+|
| | 子应用A | | 子应用B | | 子应用C ||
| | React | | Vue | | Angular ||
| +-------------+ +-------------+ +-------------+|
| |
| 路由分发 | 沙箱隔离 | 样式隔离 | 通信机制 |
+--------------------------------------------------+
三、主应用开发
3.1 注册微应用
import { registerMicroApps, start } from 'qiankun';
// 注册子应用
registerMicroApps([
{
name: 'react-app', // 应用名,唯一标识
entry: '//localhost:3001', // 子应用入口地址(HTML 或 JS URL)
container: '#container', // 挂载容器
activeRule: '/react', // 激活路由规则
props: { // 向子应用传递参数
message: 'hello'
}
},
{
name: 'vue-app',
entry: '//localhost:3002',
container: '#container',
activeRule: '/vue'
}
]);
// 启动 qiankun
start({ sandbox: { strictStyleIsolation: true } });
关键参数说明:
| 参数 | 说明 | 必填 |
|---|---|---|
name | 应用唯一名称 | ✅ |
entry | 子应用入口,可以是 URL 或 HTML 字符串 | ✅ |
container | 子应用挂载的 DOM 容器选择器 | ✅ |
activeRule | 路由激活规则,决定何时加载子应用 | ✅ |
props | 主应用向子应用传递的数据 | ❌ |
sandbox | 沙箱配置(单个应用的沙箱覆盖全局配置) | ❌ |
data | 进入子应用时携带的数据 | ❌ |
boundary | 错误边界容器选择器 | ❌ |
3.2 启动配置
start({
sandbox: {
strictStyleIsolation: false, // 严格样式隔离(使用 shadow DOM)
experimentalStyleIsolation: false, // 实验性样式隔离
strictStyleIsolation: false // Proxy 沙箱
},
prefetch: true, // 是否开启预加载
prefetchBlock: false, // 是否预加载子应用的 js/css 入口
singular: true, // 是否单实例模式
fetch, // 自定义 fetch
getPublicPath, // 自定义子应用入口地址
beforeLoad, // 加载前钩子
beforeMount, // 挂载前钩子
afterUnmount // 卸载后钩子
});
3.3 生命周期钩子
import { registerMicroApps, start, addErrorHandler } from 'qiankun';
registerMicroApps([
{
name: 'react-app',
entry: '//localhost:3001',
container: '#container',
activeRule: '/react',
// 生命周期钩子
beforeLoad: (app) => console.log('before load', app.name),
beforeMount: [(app) => console.log('before mount', app.name)],
afterUnmount: [(app) => console.log('after unmount', app.name)],
}
]);
// 全局错误处理
addErrorHandler((e) => {
console.error('qiankun error:', e);
});
生命周期说明:
| 钩子 | 时机 | 用途 |
|---|---|---|
beforeLoad | 子应用加载前 | 添加统计、日志、动态注入 script |
beforeMount | 子应用挂载前 | 准备数据、修改 DOM |
afterUnmount | 子应用卸载后 | 清理工作 |
3.4 路由集成
主应用使用 history 模式:
import { createHashHistory } from 'history';
// 主应用路由配置
const history = createHashHistory();
registerMicroApps([
{
name: 'react-app',
entry: '//localhost:3001',
container: '#container',
activeRule: '/react',
}
]);
start({ history }); // 将主应用的路由 history 传给 qiankun
子应用路由配置(子应用也需要使用 history 模式,但路由前缀由 activeRule 控制):
// React 子应用
import { BrowserRouter as Router } from 'react-router-dom';
function App() {
return (
<Router basename={window.__POWERED_BY_QIANKUN__ ? '/react' : '/'}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}
四、子应用开发
4.1 导出生命周期
子应用必须导出 bootstrap、mount、unmount 三个生命周期函数:
// src/qiankun.js
import ReactDOM from 'react-dom';
import App from './App';
let instance = null;
export async function bootstrap() {
console.log('[react] react bootstrap');
}
export async function mount(props) {
console.log('[react] react mount', props);
instance = ReactDOM.createRoot(document.getElementById('root'));
instance.render(<App />);
}
export async function unmount() {
console.log('[react] react unmount');
instance.unmount();
instance = null;
}
// 开发环境需要暴露给 qiankun
if (window.__POWERED_BY_QIANKUN__) {
// 生产环境
}
4.2 生命周期详解
| 生命周期 | 调用时机 | 说明 |
|---|---|---|
bootstrap | 应用初始化时,只调用一次 | 初始化全局资源 |
mount | 应用每次挂载时调用 | 渲染应用内容 |
unmount | 应用每次卸载时调用 | 清理资源 |
注意:
bootstrap只在第一次加载时调用mount和unmount会在每次切换时调用- 子应用需要做好状态清理,避免内存泄漏
4.3 配置 Webpack / Vite
Webpack 配置:
// webpack.config.js
const { getPublicPath } = require('qiankun');
module.exports = {
output: {
// 微服务部署需要设置 publicPath
publicPath: `//localhost:${port}`,
library: 'reactApp', // 暴露的全局变量名
libraryTarget: 'umd', // 打包格式
jsonpFunction: `webpackJsonp_${'reactApp'}`, // 避免冲突
}
};
Vite 配置:
// vite.config.js
export default defineConfig({
build: {
cssCodeSplit: false,
lib: {
entry: 'src/main.js',
formats: ['umd'],
fileName: 'index',
name: 'vueApp',
},
rollupOptions: {
external: ['vue'],
output: {
globals: {
vue: 'Vue',
},
},
},
},
});
4.4 资源路径处理
// 子应用中获取 qiankun 传递的 publicPath
import { getPublicPath } from 'qiankun';
async function setup() {
const publicPath = await getPublicPath();
console.log(publicPath);
}
五、样式隔离
5.1 CSS Scoped(默认)
qiankun 默认使用 CSS Scoped 方案,通过 JS 动态修改样式选择器实现隔离。
// 主应用配置
start({
sandbox: {
experimentalStyleIsolation: true, // 开启实验性样式隔离
}
});
原理:给子应用的 CSS 选择器添加作用域后缀,如 .app-wrapper[data-qiankun-react-app]。
5.2 Shadow DOM(严格样式隔离)
start({
sandbox: {
strictStyleIsolation: true, // 使用 Shadow DOM
}
});
优点:完全隔离,100% 样式独立。
缺点:
- 弹窗组件(Modal/Popover)会渲染在 Shadow DOM 内,无法显示在主应用区域
- 部分浏览器兼容性
- 性能开销较大
5.3 样式隔离原理
默认方案(CSS Scoped):
1. 记录子应用原始样式
2. 解析 CSSOM,修改选择器添加作用域后缀
3. 重新插入修改后的样式
Shadow DOM 方案:
1. 使用 Shadow Root 创建隔离 DOM 树
2. 子应用挂载到 Shadow Root
3. 样式天然隔离
六、JS 沙箱
6.1 为什么需要沙箱?
微前端环境下,多个子应用共享同一个浏览器全局上下文,容易出现:
- 全局变量污染
- 事件监听器冲突
window属性修改导致的应用间干扰
6.2 ProxySandbox(双例模式,默认)
start({
sandbox: {
proxySandboxType: 'proxy', // 默认
}
});
原理:
- 使用
Proxy包装window - 每个子应用拥有独立的代理 window
- 子应用对 window 的读写操作在代理层拦截
- 子应用卸载后恢复 window 状态
双例模式:
- 在沙箱环境中创建全新的 DOM 渲染树
- 子应用以为自己在操作真实的 window
- 非沙箱环境(如独立访问)使用真实 window
6.3 LegacySandbox(单例模式)
start({
sandbox: {
proxySandboxType: 'legacy',
}
});
原理:
- 所有子应用共享同一个 window
- 通过记录 + 恢复 window 属性实现隔离
- 性能更好,但隔离性较弱
6.4 沙箱对比
| 特性 | ProxySandbox | LegacySandbox |
|---|---|---|
| 隔离性 | 强 | 弱 |
| 性能 | 略低 | 略高 |
| 兼容性 | 需要 ES6 Proxy | 兼容 IE |
| 适用场景 | 大多数场景 | 需要兼容 IE |
6.5 关闭沙箱
start({ sandbox: false });
不推荐关闭,仅在特殊场景(如子应用依赖全局变量且无法改造)下使用。
七、应用间通信
7.1 props 传递(主 -> 子)
// 主应用
registerMicroApps([
{
name: 'react-app',
entry: '//localhost:3001',
container: '#container',
activeRule: '/react',
props: {
userInfo: { name: '张三', role: 'admin' },
onEvent: (data) => console.log('收到子应用事件', data)
}
}
]);
// 子应用
export function mount(props) {
console.log(props.userInfo); // { name: '张三', role: 'admin' }
console.log(props.onEvent); // 函数引用
props.onEvent({ type: 'click' }); // 调用主应用方法
}
7.2 microApp.emit / globalState(推荐)
import { microApp } from 'qiankun';
// 子应用 A 发送
microApp.emit('custom-event', { data: 'hello' });
// 子应用 B 接收
microApp.on('custom-event', (data) => {
console.log(data); // { data: 'hello' }
});
7.3 globalState(状态管理)
// 主应用
import { initGlobalState, MicroApp } from 'qiankun';
const initialState = { user: null };
const actions = initGlobalState(initialState);
// 监听状态变化
actions.subscribe((state, prevState) => {
console.log('state changed:', state, prevState);
});
// 设置新状态
actions.setState({ user: { name: '张三' } });
// 子应用获取
import { microApp } from 'qiankun';
microApp.getState(); // 获取全局状态
microApp.setState({ user: { name: '李四' } }); // 修改全局状态
7.4 其他通信方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| URL 参数 | 通过路由参数传递 | 简单数据传递 |
| localStorage | 本地存储 | 持久化数据共享 |
| window 事件 | 自定义事件 | 简单场景 |
| iframe postMessage | 跨域通信 | 跨域场景 |
八、预加载策略
8.1 为什么需要预加载?
子应用切换时需要重新加载 JS/CSS,造成白屏。预加载可以在空闲时提前加载资源,提升切换速度。
8.2 预加载配置
start({
prefetch: 'all', // 所有子应用预加载
// 或 true(默认,路由匹配到前预加载)
// 或 false(关闭预加载)
prefetchBlock: false, // 是否预加载子应用的 jsEntry/cssEntry
beforeLoad: async (app) => {
// 自定义预加载逻辑
}
});
8.3 预加载时机
应用注册 -> 路由匹配前 -> 预加载资源 -> 加载子应用
^
空闲时执行(requestIdleCallback)
九、单实例与多实例
9.1 单实例模式(默认)
start({ singular: true });
同一时间只有一个子应用处于活跃状态,卸载非活跃应用后再挂载新应用。
9.2 多实例模式
start({ singular: false });
多个子应用可以同时活跃,适用于需要同时展示多个子应用的场景。
十、进阶特性
10.1 动态微应用
import { loadMicroApp } from 'qiankun';
// 动态加载
const subApp = loadMicroApp({
name: 'dynamic-app',
entry: '//localhost:3003',
container: '#dynamic-container',
});
// 动态卸载
subApp.unmount();
10.2 子应用预加载
// 在子应用中导出 preload 生命周期
export async function preload() {
// 预加载逻辑,如预请求 API、预渲染
}
10.3 主应用生命周期
主应用也可以导出生命周期(较少用):
// 主应用也可以作为子应用被更上层的壳加载
export async function bootstrap() {}
export async function mount() {}
export async function unmount() {}
10.4 路由同步
// 主应用监听子应用路由变化
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({ pathname: '' });
// 子应用路由变化时通知主应用
window.addEventListener('qiankun-navigation', (e) => {
actions.setState({ pathname: e.detail.pathname });
});
十一、常见问题与解决方案
11.1 子应用资源 404
原因:publicPath 配置不正确。
解决:
// webpack
output: {
publicPath: `//localhost:${port}`,
}
// vite
base: `//localhost:${port}`,
11.2 样式穿透问题
/* 深度选择器 */
:deep(.some-class) {
color: red;
}
/* 或使用 /deep/ */
/deep/ .some-class {
color: red;
}
11.3 弹窗/下拉框被遮挡
使用 strictStyleIsolation 时,弹窗渲染在 Shadow DOM 内。
解决:
- 使用
getContainer配置指定弹窗挂载位置 - 或关闭严格样式隔离
11.4 内存泄漏
常见原因:
- 未清理事件监听器
- 未卸载定时器
- 未销毁组件实例
解决:
export async function unmount() {
// 清理定时器
clearInterval(timer);
// 清理事件
window.removeEventListener('resize', handler);
// 销毁组件
instance.unmount();
}
11.5 子应用独立访问
// 子应用入口
if (!window.__POWERED_BY_QIANKUN__) {
// 独立运行
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
}
十二、面试常见问题
Q1: 微前端和单体应用相比有什么优势?
答:
- 技术栈无关:不同子应用可以使用不同的框架,渐进式升级
- 独立部署:子应用可以独立构建部署,不依赖主应用
- 团队自治:不同团队可以独立开发自己的子应用
- 按需加载:只加载当前需要的子应用资源
- 故障隔离:一个子应用崩溃不影响其他子应用
Q2: qiankun 的样式隔离原理是什么?
答:
qiankun 默认使用 CSS Scoped 方案,原理是:
- 子应用加载时,qiankun 会拦截子应用注入的样式标签
- 解析 CSSOM,获取所有选择器
- 为每个选择器添加作用域后缀(如
[data-qiankun-app-name]) - 重新插入修改后的样式
- 子应用的 DOM 节点也会添加对应的属性选择器
也可以开启 strictStyleIsolation 使用 Shadow DOM,通过 Shadow Root 实现完全隔离。
Q3: qiankun 的 JS 沙箱有几种模式?有什么区别?
答:
两种模式:
-
ProxySandbox(双例模式):
- 使用 Proxy 代理 window
- 每个子应用有独立的代理 window
- 子应用卸载后恢复 window 状态
- 隔离性强,但需要 ES6 Proxy 支持
-
LegacySandbox(单例模式):
- 所有子应用共享 window
- 通过记录 + 恢复 window 属性实现隔离
- 兼容性好,支持 IE
- 隔离性较弱,可能出现变量污染
默认使用 ProxySandbox。
Q4: 子应用的生命周期有哪些?分别在什么时候调用?
答:
三个生命周期:
- bootstrap:应用初始化时调用,只调用一次,用于初始化全局资源
- mount:应用每次挂载时调用,用于渲染应用内容
- unmount:应用每次卸载时调用,用于清理资源
注意:bootstrap 只在首次加载时调用,mount 和 unmount 在每次切换时都会调用。
Q5: 主应用和子应用如何通信?
答:
几种方式:
- props 传递:主应用通过
registerMicroApps的props参数向子应用传递数据 - microApp.emit/on:子应用之间通过
microApp.emit发送事件,microApp.on接收 - globalState:通过
initGlobalState创建全局状态,子应用通过microApp.getState/setState读写 - URL 参数:通过路由参数传递简单数据
- localStorage:通过本地存储共享数据
推荐方式:简单数据用 props,复杂场景用 globalState。
Q6: qiankun 的路由是怎么工作的?
答:
- 主应用使用 history 或 hash 路由
- 子应用的
activeRule定义路由匹配规则 - 当浏览器 URL 匹配到
activeRule时,qiankun 加载并挂载对应子应用 - 子应用的路由前缀由
activeRule决定,子应用内部路由需要在basename中配置前缀 - 主应用需要将自己的 history 实例传给
start(),qiankun 会接管路由切换
Q7: 如何处理子应用的弹窗/下拉框被遮挡问题?
答:
- 如果使用了
strictStyleIsolation(Shadow DOM),弹窗会渲染在 Shadow DOM 内 - 解决方案:
- 使用
getContainer配置指定弹窗挂载到主应用的容器中 - 关闭严格样式隔离,使用 CSS Scoped
- 使用 Portal 将弹窗挂载到主应用的 DOM 节点
- 使用
Q8: qiankun 的预加载机制是什么?
答:
- 预加载在子应用路由匹配到之前提前加载资源
- 默认使用
requestIdleCallback在空闲时加载 - 配置
prefetch: true开启(路由匹配前预加载) - 配置
prefetch: 'all'预加载所有子应用 - 配置
prefetchBlock: true预加载子应用的 jsEntry/cssEntry - 预加载可以显著提升子应用切换速度
Q9: 子应用如何独立运行和微前端模式下运行?
答:
通过 window.__POWERED_BY_QIANKUN__ 判断:
if (window.__POWERED_BY_QIANKUN__) {
// 微前端模式
export { mount, unmount };
} else {
// 独立运行
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
}
独立运行时,子应用需要配置正确的 publicPath 和路由 basename。
Q10: qiankun 的 singular 模式是什么?
答:
singular: true(默认):单实例模式,同一时间只有一个子应用活跃singular: false:多实例模式,多个子应用可以同时活跃
单实例模式下,切换子应用时会先卸载旧应用再挂载新应用。多实例模式下,子应用可以同时存在,适用于需要同时展示多个子应用的场景。
Q11: 如何处理子应用之间的依赖冲突?
答:
- JS 沙箱:qiankun 的 ProxySandbox 隔离了全局变量
- CSS 隔离:样式隔离避免冲突
- 资源命名:子应用的 webpack
library和jsonpFunction使用不同名称 - 第三方库:主应用可以全局引入公共依赖,子应用通过
externals排除 - 版本统一:尽量保持子应用的依赖版本一致
Q12: qiankun 相比其他微前端方案(如 Module Federation)有什么优劣?
答:
qiankun 优势:
- 基于 single-spa,成熟稳定
- 样式隔离和沙箱机制完善
- 子应用无感知,改造成本低
- 社区活跃,中文文档完善
Module Federation 优势:
- Webpack 5 原生支持
- 共享依赖更灵活
- 性能更好(不需要隔离沙箱)
- 模块级别共享,更细粒度
选择建议:
- 老项目改造、需要样式/JS 隔离 -> qiankun
- 新项目、Webpack 5 环境 -> Module Federation
- 需要共享组件/模块 -> Module Federation
Q13: 如何优化微前端的首屏加载速度?
答:
- 预加载:开启
prefetch预加载子应用资源 - 资源压缩:Gzip/Brotli 压缩
- CDN 加速:静态资源使用 CDN
- 按需加载:子应用使用路由懒加载
- 公共依赖提取:主应用引入公共库,子应用 externals
- 预渲染:子应用首屏使用 SSR
- 骨架屏:加载期间显示骨架屏
Q14: qiankun 支持 IE 浏览器吗?
答:
- qiankun 2.x 支持 IE 11+
- 需要使用 LegacySandbox(单例模式)
- 需要 polyfill Promise 和 Proxy 的降级方案
- 样式隔离使用 CSS Scoped(Shadow DOM 不支持 IE)
- 配置:
start({ sandbox: { proxySandboxType: 'legacy' } })
Q15: 如何处理子应用的错误?
答:
- 全局错误监听:
import { addErrorHandler } from 'qiankun';
addErrorHandler((e) => {
console.error('qiankun error:', e);
});
-
子应用错误边界:使用 React Error Boundary 等机制
-
应用级错误边界:
registerMicroApps中配置boundary参数 -
子应用内部错误处理:子应用自身做好错误边界和错误上报
十三、总结
qiankun 的核心知识点可以概括为:
| 模块 | 核心要点 |
|---|---|
| 架构 | 主应用注册子应用,子应用导出生命周期 |
| 路由 | activeRule 匹配路由,history 模式同步 |
| 沙箱 | ProxySandbox(双例)/ LegacySandbox(单例) |
| 样式隔离 | CSS Scoped / Shadow DOM |
| 通信 | props / microApp.emit / globalState |
| 生命周期 | bootstrap / mount / unmount |
| 预加载 | prefetch 提升切换速度 |
| 部署 | 子应用需要配置 library / publicPath |

423

被折叠的 条评论
为什么被折叠?



