一、什么是微前端(Micro Frontends)?
微前端 就像微服务在后端的作用 —— 把一个大前端应用,拆分成多个小的、独立的子应用模块,彼此解耦,各自开发、部署、运行。
通俗来说,就是多个前端项目共存于一个页面里,各自为政但又协同工作。
举个例子:
- 主应用:框架和壳子(导航、全局状态、登录)
- 子应用:
- 商品模块(由商品团队开发)
- 订单模块(由订单团队开发)
- 用户模块(由用户团队开发)
这些子应用可以分别上线,不影响主应用和其他子应用。
二、为什么选择 qiankun?
qiankun 是阿里开源的微前端框架,基于 single-spa 做了增强,拥有以下优势:
- 接入简单,几行代码接入主子应用
- 技术栈无关:Vue、React、Angular、jQuery 都能用
- 样式隔离、JS 沙箱、安全可控
- 支持 prefetch、生命周期管理、缓存等高级特性
三、核心原理简述(简单理解即可)
主应用和子应用通信机制:
qiankun 利用了浏览器的 iframe 思想,但并不是真的用 iframe。它是通过:
- 加载子应用 HTML(如 http://localhost:7001)
- 把子应用的 DOM 插入主应用
- 执行其 JS 脚本,让它跑起来
- 它还通过沙箱技术隔离变量,避免子应用污染主应用。
四、项目搭建结构(React 示例)
我们用两个 React 应用举例:
- main-app(主应用,端口 8000)
- react-sub-app(子应用,端口 8001)
文件结构如下:
qiankun-demo/
├── main-app/
└── react-sub-app/
五、主应用 main-app 配置(React + qiankun)
1. 安装依赖
npx create-react-app main-app
cd main-app
npm install qiankun
2. 在 main-app/src/main.tsx 中注册子应用
import ReactDOM from 'react-dom/client';
import { registerMicroApps, start } from 'qiankun';
import App from './App';
registerMicroApps([
{
name: 'react-sub-app',
entry: '//localhost:8001', // 子应用地址
container: '#subapp-container', // 挂载点
activeRule: '/subapp', // 激活规则,访问 /subapp 时加载
},
]);
start(); // 启动 qiankun
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<App />);
3. 设置挂载容器 App.tsx
export default function App() {
return (
<div>
<h1>Main App</h1>
<div id="subapp-container" />
</div>
);
}
六、子应用 react-sub-app 配置
1. 安装依赖
npx create-react-app react-sub-app
cd react-sub-app
2. 暴露生命周期函数(关键)
在 src/index.tsx 中添加:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<App />);
}
// qiankun 生命周期钩子
export async function bootstrap() {
console.log('React sub app bootstraped');
}
export async function mount(props: any) {
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<App />);
}
export async function unmount() {
const root = document.getElementById('root');
if (root) root.innerHTML = '';
}
3. 设置 publicPath(不设置会找不到资源)
// config-overrides.js
module.exports = {
webpack: (config) => {
config.output.publicPath = window.__POWERED_BY_QIANKUN__ ? '/subapp/' : '/';
return config;
},
};
解释:
window.POWERED_BY_QIANKUN 是 qiankun 自动注入的全局变量。
当你只单独启动子应用时(比如访问 localhost:8001),是 独立运行模式,不会有 qiankun 这个变量,必须自己渲染应用。
如果是 qiankun 主应用加载子应用,它会注入这个变量,此时就不要自行渲染了,否则会渲染两次。
七、本地启动并测试
启动主应用
cd main-app
npm start
启动子应用
cd react-sub-app
npm start
访问 http://localhost:8000/subapp 即可看到子应用挂载成功。
八、部署到生产环境(Nginx 示例)
主应用 nginx 配置
server {
listen 80;
server_name yourdomain.com;
location / {
root /var/www/main-app;
try_files $uri /index.html;
}
location /subapp/ {
proxy_pass http://localhost:8001/;
}
}
子应用 nginx 配置
server {
listen 8001;
server_name subapp.yourdomain.com;
location / {
root /var/www/react-sub-app;
try_files $uri /index.html;
}
}
九、常见问题与解决方案
问题 | 解决方案 |
---|---|
子应用样式覆盖主应用 | 开启样式隔离(scopedCSS: true) |
子应用资源加载失败 | 设置正确 publicPath |
子应用刷新 404 | 后端设置 history 模式支持 |
子应用之间通信困难 | 使用 qiankun 的 props 或自建 eventBus |
Q1. qiankun 是如何做到多个子应用共存的?它和 iframe 有啥区别?
qiankun 利用了 HTMLEntry 技术(会加载子应用的 HTML,然后动态插入 DOM 并执行 JS),而非 iframe。
它用的是 single-spa 的注册机制 + 沙箱隔离(proxy + scope 样式隔离)。
iframe 是完全隔离的环境,而 qiankun 是在主页面里动态注入,性能更好、资源共享灵活,但需要手动处理冲突。
Q2:如果多个子应用使用了相同的依赖(如 React 版本不同),会有什么问题?怎么解决?
答:
可能出现冲突,比如 React18 和 React17 的 hooks 不兼容。
解决方案:
-
子应用配置 externals,不打包 React,让主应用统一提供(共享依赖)。
-
或者反过来让各子应用独立运行,采用 shadow DOM 或 scope css 保证隔离。
-
qiankun 支持 shared 配置,也可以通过模块联邦(Webpack 5)来优化依赖共享。
Q3:如何在主应用和子应用之间传递登录态或全局状态?
qiankun 的 registerMicroApps 中可以传 props 给子应用。
registerMicroApps([
{
name: 'react-sub-app',
entry: '//localhost:8001',
container: '#subapp-container',
activeRule: '/subapp',
props: {
token: 'xxxx', // 传递 token
userInfo: { name: 'xxxx' },
},
},
]);
子应用通过 props 参数拿到:
export async function mount(props: any) {
console.log(props.token); // 获取主应用传过来的 token
}
Q4:子应用加载成功但页面空白?
可能是因为没有设置正确的 publicPath,导致资源加载失败。
-
使用 window.POWERED_BY_QIANKUN 动态设置 publicPath。
-
或者设置 webpack 的 output.publicPath 为 ‘//localhost:8001/’。
Q5: 应用刷新时报 404 错误?
可能是子应用用了前端路由(如 React Router),刷新时 Nginx/服务端没有处理 /subapp/xxx 路由。
Nginx 配置中加上 try_files $uri /index.html,让任何路径都回到入口页。
location /subapp/ {
root /your/dist/path;
try_files $uri /index.html;
}
Q6:子应用样式污染主应用或互相干扰?
CSS 是全局的,多个应用样式可能互相覆盖。
解决方案:
-
启用 qiankun 的沙箱机制和 scopedCSS 配置。
-
或者子应用全部使用 CSS Modules / Tailwind / Shadow DOM。
start({
sandbox: {
strictStyleIsolation: true, // 开启样式隔离
},
});
Q7:子应用中事件或 setInterval 没有被清除,切换回来出现问题?
在 unmount 生命周期中一定要清理定时器、解绑事件监听等
export async function unmount() {
clearInterval(timer);
window.removeEventListener('resize', onResize);
ReactDOM.unmountComponentAtNode(document.getElementById('root')!);
}
Q8: 多个子应用注册后,页面变慢或加载冲突?
没有开启预加载、重复挂载、没有懒加载。
开启 prefetch:
start({
prefetch: true, // 提前加载子应用资源
});
或者使用 loadMicroApp 动态加载需要的子应用,避免过度注册。