记得那些年我们维护的"巨石应用"吗?一个package.json里塞满了几百个依赖,每次npm install都像是一场赌博;团队协作时,git merge冲突解决到怀疑人生;技术栈升级?那意味着"全盘推翻重来"......
随着前端复杂度的爆炸式增长,传统单体架构已不堪重负。而微前端 ,正是为了解决这些痛点而生的一种架构范式。本文将以qiankun为切入点,学习一下微前端的模式。
基础概念
微前端是什么?
微前端不是框架,而是一种架构理念 ------将大型前端应用拆分为多个独立开发、独立部署、技术栈无关的小型应用,再将其组合为一个完整的应用。
一句话,它让前端开发从"造大楼"变成了 "搭乐高" 。
为什么需要微前端?
痛点真实存在:
- 🐌 开发效率低下:几百人维护一个仓库,每次上线都需全量回归
- 🔒 技术栈锁定:三年前选的框架,现在想升级?代价巨大
- 👥 团队协作困难:功能边界模糊,代码相互渗透
- 🚢 部署风险高:一个小改动,可能导致整个系统崩溃
微前端带来的改变:
- ✅ 独立自治:每个团队负责自己的"微应用",从开发到部署全流程自主
- ✅ 技术栈自由:React、Vue、Angular、甚至jQuery,和平共处
- ✅ 增量升级:老系统可以一点点替换,而不是"一夜重构"
- ✅ 容错隔离:一个子应用崩溃,不影响其他功能
微前端的核心思想:
- 拆分:将大型前端应用拆分为多个独立的小型应用。
- 集成:通过某种方式将这些小型应用集成在一起,形成一个整体。
- 自治:每个小型应用都可以独立开发、测试、部署。
js
// 微前端架构
├── container/ // 主应用(基座)
├── app-react/ // React子应用(团队A)
├── app-vue/ // Vue子应用(团队B)
├── app-angular/ // Angular子应用(团队C)
└── app-legacy/ // 老系统(jQuery)
// 优势:
// 1. ✅ 技术栈无关
// 2. ✅ 独立开发、独立部署
// 3. ✅ 增量更新
// 4. ✅ 容错性高(一个子应用挂了不影响其他)
应用场景
渐进式重构:对于一个老项目一点点进行架构的升级
老系统(jQuery + PHP) → 逐步替换为现代框架
↓
保留核心业务模块 + 逐步添加React/Vue新模块
多团队协作:不同部门人员之间技术栈存在差异,需要单独开发
css
团队A(React专家) → 负责电商商品模块
团队B(Vue专家) → 负责购物车模块
团队C(Angular专家)→ 负责用户中心
主应用协调所有模块
中后台系统:复杂系统的功能拆分
diff
一个后台管理系统包含:
- 权限管理(React)
- 数据报表(Vue + ECharts)
- 工作流(Angular)
- 监控面板(React + Three.js)
四种架构模式
基座模式(也称为中心化路由模式)
- 基座模式是最常见的微前端架构。它有一个主应用(通常称为基座或容器),负责整个应用的布局、路由和公共逻辑。子应用根据路由被动态加载和卸载。
scss
┌─────────────────────────────────────────┐
│ 主应用(Container) │
│ 负责:路由、鉴权、布局、共享状态、公共依赖 │
├─────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ 子应用A │ │ 子应用B │ │ 子应用C │ │
│ │ (React) │ │ (Vue) │ │(Angular) │
│ └──────────┘ └──────────┘ └──────────┘
└─────────────────────────────────────────┘
工作流程
优点
- 集中控制,易于管理
- 路由逻辑清晰
- 公共依赖容易处理(基座可提供共享库)
- 子应用间隔离性好
缺点
- 主应用成为单点故障
- 基座和子应用耦合(通过协议通信)
- 基座需要知道所有子应用的信息
适用场景
- 企业级中后台系统
- 需要统一导航和布局的应用
- 子应用技术栈差异大
自组织模式(也称为去中心化模式)
- 在自组织模式中,没有中心化的基座。每个微前端应用都是独立的,它们通过某种通信机制(如自定义事件、消息总线)来协调。通常,每个应用都可以动态发现和加载其他应用。
scss
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 应用A │ │ 应用B │ │ 应用C │
│ (React) │ │ (Vue) │ │(Angular) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└───────────────┼───────────────┘
│
┌────────┴─────────┐
│ 运行时协调器 │
│ (Runtime Bus) │
└──────────────────┘
优点
- 去中心化,避免单点故障
- 应用之间完全解耦
- 更灵活的通信方式
缺点
- 通信复杂,容易混乱
- 难以统一管理(如路由、权限)
- 依赖公共协议,版本更新可能破坏通信
适用场景
- 高度自治的团队
- 应用间功能相对独立
- 需要动态组合的页面
微件模式(也称为组合式模式)
-
微件模式类似于传统门户网站,页面由多个独立的微件(Widget)组成。每个微件都是一个独立的微前端应用,可以独立开发、部署,然后动态组合到页面中。
┌───────────────────────────────────┐
│ Dashboard页面 │
│ ┌────────┬────────┬─────────┐ │
│ │ 天气 │ 新闻 │ 股票 │ │
│ │ Widget │ Widget │ Widget │ │
│ ├────────┼────────┼─────────┤ │
│ │ 待办 │ 日历 │ 邮件 │ │
│ │ Widget │ Widget │ Widget │ │
│ └────────┴────────┴─────────┘ │
└───────────────────────────────────┘
优点:
- 组件可以复用
- 用户可以自定义布局
- 所有widget在同一个页面
- 可以按需加载widget
缺点:
- 样式管理复杂,需要处理widget间样式冲突
- 通信限制,widget间通信需要经过主应用
- 版本管理,大量widget的版本管理困难
- 性能问题,太多widget可能影响性能
适用场景:
- 数据可视化大屏
- 门户网站首页
- 个人工作台
- 可配置的管理后台
混合模式(实战中最常见)
- 在实际项目中,我们常常根据需求混合使用以上模式。例如,在基座模式中,某个子应用内部使用微件模式来组合多个微前端模块。
- 比如一个电商系统的架构
css
主应用(基座模式)
├── 商品管理(React子应用)
├── 订单管理(Vue子应用)
└── 用户管理(Angular子应用)
在用户管理内部,使用微件模式:
├── 用户统计(微件A)
├── 用户列表(微件B)
└── 用户权限(微件C)
scss
┌─────────────────────────────────────────────────┐
│ 主应用(基座模式) │
│ 统一路由、权限、用户中心、消息中心、全局状态 │
└─────────────────┬───────────────────────────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌───▼───┐ ┌────▼────┐ ┌────▼────┐
│订单中心│ │商品管理 │ │用户管理 │
│(React)│ │ (Vue) │ │(Angular)│
└───┬───┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼─────────────┘
│
┌──────▼──────┐
│ 数据分析模块 │
│ (微件模式) │
│┌───┬───┬───┐│
││图表│地图│报表│
│└───┴───┴───┘│
快速上手
- 新建三个项目,分别为
main-app,sub-app1,sub-app2,项目结构一目了然:
less
├── main-app/ // 主应用(基座)
├── sub-app1/ // vue3子应用(团队A)
├── app-vue/ // vue3子应用(团队B)
安装qiankun
shell
yarn add qiankun # 或者 npm i qiankun -S
主项目中注册微应用
js
// 主应用main-app/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { registerMicroApps, start } from 'qiankun'
createApp(App).mount('#app1')
registerMicroApps(
[
{
name: 'sub-app1', // app name registered
entry: 'http://localhost:5175',
container: '#micro-app-container',
activeRule: (location) => location.hash.startsWith('#/app-a'),
props: {
name: 'kuitos'
}
}
],
{
beforeLoad: (app) => console.log('before load', app.name),
beforeMount: [(app) => console.log('before mount', app.name)]
}
)
// start()
// 启动 qiankun,配置沙箱模式
start({
sandbox: {
strictStyleIsolation: true,
},
})
微应用导出钩子
- 由于
qiankun不支持module,所以对于vue3项目,需要使用vite-plugin-qiankun来集成 renderWithQiankun用来对外暴露钩子qiankunWindow替代window变量
js
// 子应用 sub-app1/mian.js
import { createApp } from 'vue'
import {
renderWithQiankun,
qiankunWindow
} from 'vite-plugin-qiankun/dist/helper'
import './style.css'
import App from './App.vue'
let instance = null
function render(props = {}) {
const container = props.container || '#app'
console.log('子应用挂载容器:', container)
instance = createApp(App)
instance.mount(container)
}
console.log('qiankunWindow',qiankunWindow);
console.log('window.__POWERED_BY_QIANKUN__',window.__POWERED_BY_QIANKUN__);
// 独立运行时,直接渲染
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
console.log('独立运行时,直接渲染')
render()
}
renderWithQiankun({
mount(props) {
console.log(props)
render(props)
},
bootstrap() {
console.log('bootstrap')
},
unmount(props) {
console.log('unmount', props)
},
update(props) {
console.log('update', props)
}
})
在子应用的vite.config.js中注册插件
js
// 子应用 sub-app1/vite.config.js
plugins: [
vue(),
qiankun('sub-app1', {
useDevMode: true,
})
],
进阶场景
应用通信
应用拆分后,不可避免的会涉及到通信问题,那么如何让它们"愉快地对话"?
props
- 最简单的方式,正如目前的主流框架,
qiankun也提供了一个props属性,可以实现父->子之间的数据通信,当主应用注册registerMicroApps子应用的时候,利用props传递
js
// 主应用 main-app/main.js
registerMicroApps(
[
{
name: 'sub-app1', // app name registered
entry: 'http://localhost:5175',
container: '#micro-app-container',
activeRule: (location) => location.hash.startsWith('#/app-a'),
props: {
// name: 'kuitos' //该属性会被覆盖?
count: 100,
time: new Date().getTime()
}
}
],
{
beforeLoad: (app) => console.log('before load', app.name),
beforeMount: [(app) => console.log('before mount', app.name)]
}
)
js
// 子应用 sub-app1/main.js
renderWithQiankun({
mount(props) {
render(props)
},
})
js
// 子应用 sub-app1/main.js
function render(props = {}) {
const container = props.container || '#app'
console.log('子应用挂载容器:', container)
instance = createApp(App)
instance.config.globalProperties.__SUBAPP__ = props //vue3的globalProperties全局挂载
window.__SUBAPP__ = props //挂载到子应用的window对象
instance.mount(container)
}
子应用的其他组件使用时
vue
//子应用 sub-app1/src/components/HelloWord.vue
<script setup>
import { ref,getCurrentInstance } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
console.log('window方式获取数据',window.__SUBAPP__)
console.log('getCurrentInstance方式获取数据', getCurrentInstance().proxy.__SUBAPP__)
</script>

initGlobalState
父->子传递
首先在父应用中创建一个globalState.js初始化一下state
js
//main-app/globalState.js
import { initGlobalState } from 'qiankun';
// 定义初始状态
export const initialState = {
user: { id: null, name: '', token: '' },
globalConfig: { theme: 'light', language: 'zh-CN' },
sharedData: {},
currentRoute: {}
};
// 当前全局状态
export let currentGlobalState = { ...initialState };
// 全局状态管理器实例
export let globalActions = null;
// 初始化全局状态管理
export const initGlobalStateManager = () => {
// 初始化 state
const actions = initGlobalState(initialState);
// 监听状态变更
actions.onGlobalStateChange((state, prev) => {
currentGlobalState = { ...state };
console.log('主应用:全局状态变更', { newState: state, prevState: prev });
});
// 设置初始状态
actions.setGlobalState(initialState);
globalActions = actions;
return actions;
};
// 更新全局状态
export const updateGlobalState = (newState) => {
if (!globalActions) {
globalActions = initGlobalStateManager();
}
globalActions.setGlobalState(newState);
};
其中关键方法:
js
// 定义初始状态
export const initialState = {
user: { id: null, name: '', token: '' },
globalConfig: { theme: 'light', language: 'zh-CN' },
sharedData: {},
currentRoute: {}
};
//初始化 state
const actions = initGlobalState(initialState);
// 监听状态变更
actions.onGlobalStateChange((state, prev) => {
currentGlobalState = { ...state };
console.log('主应用:全局状态变更', { newState: state, prevState: prev });
});
// 更新全局状态
actions.setGlobalState(newState);
// 取消监听
actions.offGlobalStateChange();
js
// main-app/login.vue
import { updateGlobalState } from './globalState'
const handleLogin =()=>{
// 。。。主应用的业务逻辑
// 更新state
updateGlobalState({
isLoggedIn: true,
});
}
在子应用中监听
js
// sub-app1/main.js
function render(props = {}) {
const container = props.container || '#app'
instance = createApp(App)
// 监听全局状态变化
//props 里面有setGlobalState和onGlobalStateChange 方法,可用于监听和修改状态
if (props.onGlobalStateChange) {
props.onGlobalStateChange((state, prev) => {
console.log('子变更后的状态', state, '子变更前的状态', prev);
});
}
// 挂载一下props,以便于在其他组件中使用setGlobalState和onGlobalStateChange
// 挂载的方式有很多, pinia等,总之其他地方能获取到props对象就行
window.__SUBAPP__ = props
pinia = createPinia()
instance.use(pinia)
instance.mount(container)
}

子->父传递
在子应用创建的时候,已经将props保存了window.__SUBAPP__ = props,在子应用的任何组件中都可以使用
所以只需要在某个组件中调用setGlobalState方法就可
js
// sub-app1/HelloWord.vue
// 获取全局状态管理方法
const { setGlobalState } = window.__SUBAPP__ || {}
if (setGlobalState) {
// 更新全局状态
setGlobalState({
sharedData: {
count: newValue
}
})
}

微前端选型指南:何时用?用哪个?
适合场景 ✅
- 大型企业级应用(100+页面)
- 多团队协作开发(3+前端团队)
- 老系统渐进式重构
- 需要支持多技术栈
- 独立部署需求强烈
不适合场景 ❌
- 小型项目(页面<20)
- 单人/小团队开发
- 对性能要求极致(首屏加载时间<1s)
- 无技术栈异构需求
结语
千万不要手里攥着锤子看啥都像钉子。 微前端不是银弹,而是一种架构选择。它用复杂度换来了灵活性、独立性和可维护性。就像乐高积木,单个模块简单,但组合起来却能构建出无限可能的世界。
后续有时间将继续深入学习一下微前端的生命周期、样式隔离、部署发布这几个部分。
最后,觉得有用的话三连一下~