本文基于当前 micro-demo 项目,介绍什么是微前端、为什么要使用微前端,以及这个 Demo 如何通过 qiankun 实现一个 React 基座应用加载 React 子应用。
项目结构如下:
text
micro-demo
├── main-react # 微前端基座应用
└── sub-react # React 子应用
1. 什么是微前端
微前端可以理解为前端领域里的"微服务"思想:把一个大型前端应用拆成多个可以独立开发、独立运行、独立构建、独立部署的小应用,再由一个主应用把它们组合起来。
在传统单体前端中,所有页面、路由、状态、依赖和构建流程通常都放在同一个项目里。随着业务增长,这种模式会遇到一些问题:
- 项目越来越大,启动和构建变慢。
- 多个团队在同一个代码仓库里协作,容易互相影响。
- 技术栈升级困难,例如想把部分旧页面从 Vue 迁移到 React。
- 某个业务模块上线,需要跟整个前端项目一起发版。
微前端的核心目标就是降低这些耦合。它允许不同业务模块以子应用的形式存在,每个子应用可以有自己的技术栈、依赖、路由和发布节奏。
2. 微前端解决什么问题
微前端并不是为了让项目变复杂,而是为了解决中大型前端项目中的组织和演进问题。
常见使用场景包括:
- 多团队协作:不同团队负责不同子应用,减少代码冲突。
- 老项目渐进式改造:保留原有系统,同时逐步迁移部分模块。
- 多技术栈共存:主应用可以加载 React、Vue、Angular 等不同技术栈的子应用。
- 独立部署:一个子应用更新,不必重新发布所有前端代码。
- 业务边界清晰:按业务域拆分应用,例如订单、用户、报表、权限等模块。
当然,微前端也有成本,例如运行时集成、样式隔离、依赖重复、通信约定、部署配置等。因此,小型项目通常不需要一开始就引入微前端。
3. qiankun 的基本思路
当前项目使用的是 qiankun。qiankun 是一个基于 single-spa 的微前端框架,它主要负责几件事:
- 注册子应用。
- 根据当前 URL 判断应该激活哪个子应用。
- 加载子应用入口资源。
- 把子应用挂载到主应用指定的 DOM 容器中。
- 在切换路由时卸载旧子应用。
- 提供生命周期机制,例如
bootstrap、mount、unmount。
在 qiankun 模式下,通常会有两类应用:
- 基座应用:负责整体布局、导航、注册子应用和提供挂载容器。
- 子应用:负责自己的页面内容,并暴露符合 qiankun 约定的生命周期。
当前 Demo 中:
main-react是基座应用。sub-react是子应用。- 访问基座的
/sub-react路径时,qiankun 会加载sub-react。
4. 当前项目的目录说明
4.1 基座应用 main-react
main-react 的关键文件:
text
main-react
├── index.html
├── package.json
├── vite.config.js
└── src
├── main.jsx
└── style.css
它依赖:
json
{
"qiankun": "^2.10.16",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
基座应用的职责是:
- 渲染页面标题和导航。
- 提供
#subapp-container作为子应用挂载容器。 - 使用
registerMicroApps注册子应用。 - 调用
start()启动 qiankun。
4.2 子应用 sub-react
sub-react 的关键文件:
text
sub-react
├── index.html
├── package.json
├── vite.config.js
└── src
├── main.jsx
└── App.jsx
它依赖:
json
{
"vite-plugin-qiankun": "^1.0.15",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
子应用的职责是:
- 在独立运行时,可以自己渲染到自己的
#root。 - 被 qiankun 加载时,按照 qiankun 生命周期挂载和卸载。
- 通过
vite-plugin-qiankun适配 Vite 构建和 qiankun 运行时。
5. 如何运行这个 Demo
这个 Demo 需要同时启动基座应用和子应用。
先启动子应用:
bash
cd sub-react
npm run dev
子应用端口在 sub-react/vite.config.js 中被固定为 8001:
js
server: {
port: 8001,
cors: true,
}
然后启动基座应用:
bash
cd ../main-react
npm run dev
基座应用没有固定端口,Vite 默认通常会启动在:
text
http://localhost:5173
打开基座地址后:
- 访问
/时显示基座首页。 - 点击"React 子应用"后,URL 变成
/sub-react。 - qiankun 命中子应用的
activeRule,加载http://localhost:8001。 - 子应用内容被渲染到基座的
#subapp-container中。
6. 基座应用如何实现微前端加载
基座核心代码在 main-react/src/main.jsx。
首先,基座正常渲染一个 React 应用:
jsx
function App() {
return (
<div>
<h1>React18 微前端基座</h1>
<nav>
<a href="/">首页</a>
<a href="/sub-react">React 子应用</a>
</nav>
<div id="subapp-container">
<h2>基座首页</h2>
<p>点击上方菜单加载 React 子应用。</p>
</div>
</div>
);
}
这里最重要的是这个容器:
html
<div id="subapp-container"></div>
它是子应用最终要挂载的位置。
然后基座注册子应用:
jsx
registerMicroApps([
{
name: "sub-react",
entry: "//localhost:8001",
container: "#subapp-container",
activeRule: "/sub-react",
},
]);
这段配置的含义是:
| 字段 | 含义 |
|---|---|
name |
子应用名称,需要保持唯一 |
entry |
子应用入口地址,这里是 //localhost:8001 |
container |
子应用要挂载到基座的哪个 DOM 容器 |
activeRule |
当 URL 匹配 /sub-react 时激活这个子应用 |
最后启动 qiankun:
jsx
start();
调用 start() 后,qiankun 会开始监听路由变化。当浏览器地址匹配 /sub-react 时,就会请求并加载子应用资源。
7. 子应用如何接入 qiankun
子应用核心代码在 sub-react/src/main.jsx。
子应用首先定义自己的渲染函数:
jsx
function render(container) {
const rootElement = container
? container.querySelector("#root")
: document.querySelector("#root");
root = ReactDOM.createRoot(rootElement);
root.render(<App />);
}
这里有一个关键判断:
- 如果有
container,说明当前是被 qiankun 加载,应该在 qiankun 提供的容器里找#root。 - 如果没有
container,说明当前是子应用独立运行,直接从document上找#root。
然后子应用通过 renderWithQiankun 暴露生命周期:
jsx
renderWithQiankun({
bootstrap() {
console.log("sub-react bootstrap");
},
mount(props) {
console.log("sub-react mount", props);
render(props.container);
},
unmount() {
console.log("sub-react unmount");
root.unmount();
root = null;
},
});
这三个生命周期分别表示:
| 生命周期 | 触发时机 | 当前项目中的作用 |
|---|---|---|
bootstrap |
子应用第一次初始化时 | 打印初始化日志 |
mount |
子应用被激活时 | 调用 render 渲染 React 应用 |
unmount |
子应用被切走时 | 调用 root.unmount() 卸载 React 应用 |
最后这段代码保证子应用可以独立运行:
jsx
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render();
}
如果不是在 qiankun 环境下运行,就直接执行 render()。因此 sub-react 既可以被基座加载,也可以直接访问 http://localhost:8001 独立运行。
8. 子应用的 Vite 配置
子应用的配置在 sub-react/vite.config.js。
js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import qiankun from "vite-plugin-qiankun";
export default defineConfig({
plugins: [
react(),
qiankun("sub-react", {
useDevMode: true,
}),
],
server: {
port: 8001,
cors: true,
},
base: "http://localhost:8001/",
});
这里有三个重点。
第一,使用 vite-plugin-qiankun:
js
qiankun("sub-react", {
useDevMode: true,
})
它帮助 Vite 子应用适配 qiankun 的加载方式。
第二,固定开发端口:
js
server: {
port: 8001,
cors: true,
}
基座的 entry 写的是 //localhost:8001,所以子应用必须运行在 8001 端口。cors: true 是为了允许基座跨端口加载子应用资源。
第三,配置资源基础路径:
js
base: "http://localhost:8001/",
这样子应用资源在被基座加载时,可以从正确的地址请求。
9. 一次完整的切换流程
以用户点击"React 子应用"为例,完整流程如下:
- 用户打开基座应用,例如
http://localhost:5173。 - 基座渲染标题、导航和
#subapp-container容器。 - 基座调用
registerMicroApps注册sub-react。 - 基座调用
start()启动 qiankun。 - 用户点击导航链接
/sub-react。 - 浏览器 URL 变为
http://localhost:5173/sub-react。 - qiankun 发现当前路径匹配
activeRule: "/sub-react"。 - qiankun 请求子应用入口
//localhost:8001。 - 子应用资源加载完成后,执行
bootstrap。 - qiankun 调用子应用的
mount(props)。 - 子应用从
props.container中找到自己的#root。 - 子应用通过
ReactDOM.createRoot渲染App。 - 页面中
#subapp-container的内容被替换为子应用内容。 - 用户回到
/或切换到其他子应用时,qiankun 调用unmount卸载当前子应用。
这就是当前 Demo 中"点击菜单切换到子应用"的完整链路。
10. 当前 Demo 的特点和边界
这个 Demo 已经覆盖了微前端最核心的流程:
- 基座应用注册子应用。
- 子应用支持 qiankun 生命周期。
- 子应用可以被基座加载。
- 子应用也可以独立运行。
- 通过 URL 路由完成应用切换。
不过它还是一个最小示例,暂时没有处理一些生产环境中常见的问题:
- 多个子应用的注册和切换。
- 基座和子应用之间的数据通信。
- 样式隔离和全局样式污染。
- 公共依赖复用。
- 权限、菜单、面包屑等基座能力下发。
- 生产环境部署路径和 Nginx 配置。
- 子应用加载失败、loading、错误兜底。
这些内容通常会在真实业务中逐步补齐。
11. 如果继续扩展这个项目
可以按下面几个方向继续完善:
- 增加第二个子应用,例如
sub-vue或sub-react-admin。 - 给基座增加统一布局,例如侧边栏、顶部栏和内容区。
- 使用 qiankun 的
props给子应用传递用户信息、主题配置或公共方法。 - 使用
initGlobalState实现基座和子应用之间的状态通信。 - 给
#subapp-container增加 loading 和错误兜底。 - 为子应用增加独立路由,演示
/sub-react/list、/sub-react/detail等页面。 - 编写生产部署说明,让基座和子应用可以分别构建、分别部署。
12. 总结
微前端的本质是把一个大型前端系统拆成多个可以独立交付的小应用,再通过基座应用在运行时把它们组合起来。
在当前项目中:
main-react扮演基座角色。sub-react扮演子应用角色。- 基座通过
registerMicroApps注册子应用。 activeRule: "/sub-react"决定什么时候加载子应用。container: "#subapp-container"决定子应用挂载到哪里。- 子应用通过
renderWithQiankun暴露bootstrap、mount、unmount生命周期。 qiankunWindow.__POWERED_BY_QIANKUN__用来判断当前是独立运行还是被基座加载。
这个 Demo 虽然简单,但已经体现了微前端的核心闭环:基座注册、路由匹配、资源加载、生命周期挂载、切换卸载。