一. 前置
本文假设你已经:
- 熟悉至少一种现代前端框架(Vue 或 React)。
- 理解单页应用 的基本概念和开发模式。
- Webpack 的基本概念和基本原理。
前三四五章帮助快速理解微前端并且学会如何接入
后面章节为深入原理解析
二. 本文能给你带来什么
读完本文,你将获得:
- 理解微前端架构的价值
- 亲手搭建一个包含 Vue 主应用和 React/Vue 子应用的完整
qiankun项目。 - 深入
qiankun的 JS 沙箱 和 样式隔离 的 底层原理 并能够解决实际简单问题。 - 掌握 应用 之间 数据传递 和 状态同步 的各种方式和优缺点。
三. 简单说微前端要干什么
它主要解决了三大难题:
- 技术栈异构
整个项目被锁定在 单一技术栈 上, 微前端使得每个微应用都可以使用任何技术栈,由主应用分别进行加载。
- 降低维护成本,拆分巨石应用
大型应用所有代码耦合在一起,编译部署慢,新人上手成本高。微前端可以按业务拆分应用。
- 减少项目团队协作问题,团队独立开发与部署
多团队在 同个代码库协作 ,代码冲突 、依赖问题 频发,发布流程又慢又危险。 微前端使得每个团队拥有自己微应用的代码库,可以独立决策、开发、部署。
四. qiankun 怎么运行的
一个简易流程图:
qiankun 的工作流程可以分解为以下几个核心步骤:
1. 主应用注册子应用信息
- 在主应用启动时,你需要告诉
qiankun你有哪些子应用,并提供几个关键信息:
name: 给子应用起一个唯一的英文名,作为它的身份标识。entry: 子应用独立运行时的访问地址,比如http://localhost:8081。qiankun会通过这个地址去获取子应用的资源。container: 主应用里一个空的<div>元素的 ID,告诉qiankun将来把这个子应用渲染到哪里。activeRule: 一个路径规则,比如/app1。当浏览器 URL 匹配到这个规则时,qiankun就知道该加载这个子应用了。
2. 监听路由变化动态加载子应用
- 主应用启动
qiankun后,它会开始监听浏览器地址栏的 URL 变化,并拿新的 URL 去和第一步注册的所有子应用的activeRule进行匹配。一旦匹配成功,就触发下一步。
3. 从 entry 获取子应用内容
qiankun匹配到要加载的子应用后,它会向该子应用的entry地址发送一个 HTTP 请求并拿到返回的index.html文件。- 之后,
qiankun调用解析器一样,分析这个文本,从中找出所有<script>标签的src地址和<link rel="stylesheet">或<style>标签里的 CSS 内容。 - 至此,
qiankun就拿到了一份关于这个子应用需要加载的所有 JS 和 CSS 资源的清单。
4. 执行子应用JS
-
qiankun为子应用创建一个"代理"的window对象。 -
当子应用的 JS 代码尝试写入 全局变量时(如
window.myVar = 1),Proxy会拦截这个操作,并将这个变量存储在一个只属于当前子应用的内部对象里,而不会 污染真实的window对象。 -
当子应用的 JS 代码尝试读取 全局变量时(如
console.log(window.myVar)),Proxy会先从那个内部对象里查找。如果找不到,它会安全地"穿透"到真实的window对象上去读取(比如读取window.location)。
5. 插入子应用 CSS
qiankun默认以<style>标签的形式,将 css 直接插入 到主文档的<head>之中- 样式都是全局的,子应用之间、子应用与主应用之间会相互污染,怎么解决可以看下面精讲
6. 子应用生命周期
qiankun要求每个子应用都必须导出几个特定的函数(bootstrap,mount,unmount)。- 在完成上述所有准备工作后,
qiankun会调用子应用的mount函数,并把前面指定的container容器作为参数传给它。子应用在mount函数里执行自己的渲染逻辑(比如ReactDOM.render()或createApp().mount()),把自己渲染到那个容器里。 - 当 URL 变化,需要切换到另一个子应用时,
qiankun会先调用当前子应用的unmount函数,让它执行清理工作(比如销毁实例、清除定时器),然后再去加载新的子应用。
五. Vue 3 + React + qiankun 搭建流程
这一章带你运用上面的原理从头搭建一个超简单 qiankun 工程,并梳理运行流程
看效果:

前置
准备一个 webpack 作为打包器的主应用以及两个子应用(qiankun 对 webpack 支持更好更方便),并且要求都是单页应用。
第一步:主应用 Vue3 (main-app) 配置
- 安装 qiankun:
csharp
pnpm add qiankun
- 创建 qiankun 配置文件(对应注册子应用) :
main-app/src/qiankun-setup.js
js
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'sub-app-vue',
entry: '//localhost:8081', // 你的 Vue 应用端口
container: '#subapp-container',
activeRule: '/vue-app',
},
{
name: 'sub-app-react',
entry: '//localhost:3000', // 你的 React 应用端口
container: '#subapp-container',
activeRule: '/react-app',
},
];
export const setupQiankun = () => {
registerMicroApps(apps);
start();
};
- 在
main.js中启动 qiankun(对应qiankun开始监听路由):
js
// ... existing code ...
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { setupQiankun } from './qiankun-setup'; // 引入
createApp(App).use(router).mount('#app');
setupQiankun(); // 启动
- 在
App.vue中设置容器和导航:
js
<template>
<div id="main-app">
<header>
<router-link to="/">主页</router-link> |
<router-link to="/vue-app">访问Vue子应用</router-link> |
<router-link to="/react-app">访问React子应用</router-link>
</header>
<!-- 主应用路由渲染 -->
<router-view />
<!-- 子应用容器 -->
<div id="subapp-container"></div>
</div>
</template>
// ... existing code ...
第二步:Vue 子应用 (sub-app-vue) 改造
- 配置
vue.config.js以支持跨域和 qiankun 的模块规范。
js
const { defineConfig } = require('@vue/cli-service');
const { name } = require('./package.json');
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8081,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`, // 配合libraryTarget使用,挂载到全局时的名字
libraryTarget: 'umd', // 把微应用打包成 umd 库格式,便于将导出的 bootstrap、mount、unmount 等函数挂载到全局
chunkLoadingGlobal: `webpackJsonp_${name}`,// 懒加载函数名字,防止不同应用间在懒加载时的冲突
},
},
});
为接入 qiankun,需在
vue.config.js中配置 Webpack:
output.libraryTarget: 'umd':将子应用打包成通用模块,以便 qiankun 能识别并获取其导出的生命周期函数。output.library: '${name}-[name]':为打包后的库设置唯一名称,防止多子应用在全局window上的命名冲突。output.chunkLoadingGlobal: 'webpackJsonp_${name}':为 Webpack 懒加载函数设置唯一名称,避免不同应用间的 chunk 加载逻辑互相覆盖。
- 添加入口文件
public-path.js:sub-app-vue/src/public-path.js
js
if (window.__POWERED_BY_QIANKUN__) {
// 运行时动态修改所有静态资源路径
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
在 qiankun 架构中,子应用不知道自己的静态资源(如图片、JS chunk)应从何处加载,因为浏览器默认会从主应用域名请求,导致 404 错误。
为解决此问题,qiankun 向子应用注入了window.__POWERED_BY_QIANKUN__标志位和window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__变量(值为子应用的 entry 地址)。子应用在入口文件判断若在 qiankun 环境中,就将这个注入的路径赋值给 Webpack 的__webpack_public_path__。这样,所有资源请求的 URL 前缀都会在运行时被动态修正,从而确保能正确加载。
- 改造
main.js,导出 qiankun 需要的生命周期。
js
import './public-path'; // 必须放在顶部
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
let instance = null;
function render(props = {}) {
const { container } = props;
instance = createApp(App);
instance
.use(router)
.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
export async function mount(props) {
console.log('[vue] props from main framework', props);
render(props);
}
export async function unmount() {
instance.unmount();
instance = null;
}
第三步:React 子应用 (sub-app-react) 改造
- 安装
react-app-rewired
css
npm install react-app-rewired --save-dev
react-app-rewired能在不执行 "eject"(暴露配置)的前提下,通过创建config-overrides.js文件来覆盖 Webpack 配置。只需将package.json中的react-scripts命令替换为react-app-rewired,即可轻松实现output和devServer的定制,是 CRA 项目接入 qiankun 的必备工具。
- 修改
package.json的scripts:
json
// ... existing code ...
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
// ... existing code ...
- 创建
config-overrides.js:sub-app-react/config-overrides.js
js
const { name } = require('./package.json');
module.exports = {
webpack: (config) => {
config.output.library = `${name}-[name]`;
config.output.libraryTarget = 'umd';
config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;
return config;
},
devServer: (configFunction) => {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.headers = {
'Access-Control-Allow-Origin': '*',
};
return config;
};
},
};
- 添加入口文件
public-path.js:sub-app-react/src/public-path.js
js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 改造
index.js,导出生命周期。
js
import './public-path'; // 必须放在顶部
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
let root = null;
function render(props) {
const { container } = props;
const dom = container ? container.querySelector('#root') : document.getElementById('root');
root = ReactDOM.createRoot(dom);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('[react] react app bootstraped');
}
export async function mount(props) {
console.log('[react] props from main framework', props);
render(props);
}
export async function unmount() {
if (root) {
root.unmount();
root = null;
}
}
六. JS 沙箱验证
JS 沙箱验证
Proxy Sandbox 是如何在不污染全局 window 的情况下工作的?我们来验证一下。
- 在 Vue 子应用中设置全局变量 :
sub-app-vue/src/views/HomeView.vue
js
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
window.myGlobalVar = '我来自Vue子应用';
console.log('【Vue子应用】设置了全局变量 window.myGlobalVar');
});
</script>
- 在 React 子应用中读取该变量 :
sub-app-react/src/App.js
js
import React, { useEffect } from 'react';
// ...
function App(props) {
useEffect(() => {
console.log('【React子应用】尝试读取 window.myGlobalVar:', window.myGlobalVar);
}, []);
// ...
}
export default App;
实验步骤与结论:
- 先访问 Vue 子应用,控制台打印"设置了全局变量"。
- 再切换到 React 子应用,控制台打印"尝试读取 window.myGlobalVar: undefined"。
- 结论 :Vue 子应用对
window的写入被Proxy沙箱拦截,并储存在了自己的"独立化妆间"里,并未影响到真实的window,因此 React 子应用无法读取到它。 样式隔离验证
七. CSS 沙箱验证
qiankun 通过三种不同的策略来解决样式冲突问题,我们来逐一讲解。
1. 动态样式隔离
这是 qiankun 默认启用的样式隔离方案。它的工作原理非常简单:
- 加载时 :
qiankun会遍历子应用 HTML 中的所有<style>和<link>标签,将 CSS 内容记录下来,然后动态创建新的<style>标签,把这些内容插入到主应用的<head>中。 - 卸载时 :
qiankun会找到并移除之前为该子应用添加的所有<style>标签。
它确保了在"单实例"场景下(即任何时候只有一个子应用在运行),后加载的应用样式不会被先前的应用样式污染。
缺点(这也是最关键的) :
- 无法隔离主应用与子应用 :由于子应用的样式被直接插入主文档,如果选择器命名不当(例如,
.btn),主应用的样式很容易被子应用覆盖,反之亦然。 - 无法隔离多实例子应用:如果同一个子应用需要被实例化多次并同时展示,它们的样式会相互冲突,因为它们共享相同的 CSS 规则。
验证默认隔离的局限性
我们来复现一下主应用被污染的场景。
- 在主应用中定义一个全局样式 :
main-app/src/App.vue
html
<template>
<div id="main-app">
<header>
<!-- ... -->
<button class="common-button">主应用按钮</button>
</header>
<!-- ... -->
<div id="subapp-container"></div>
</div>
</template>
<style>
/* 一个非常通用的类名 */
.common-button {
background-color: #42b983; /* 绿色 */
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
}
</style>
- 在 Vue 子应用中定义一个同名但样式不同的类 :
sub-app-vue/src/views/HomeView.vue
html
<template>
<div>
<h3>Vue 子应用</h3>
<button class="common-button">Vue子应用按钮</button>
</div>
</template>
<style>
/* 子应用也定义了一个同名类 */
.common-button {
background-color: #f0ad4e; /* 橙色 */
border-radius: 8px;
}
</style>
实验步骤与结论:
- 启动主应用,主应用的按钮是绿色的。
- 切换到 Vue 子应用,你会发现子应用的按钮是橙色的,并且有圆角。
- 问题出现了 :此时你再回头看主应用的按钮,它也变成了橙色 和圆角。

所以该方案并不推荐使用
2. Shadow DOM 沙箱
这是一种需要手动开启的、隔离性最强的方案。
- 原理 :
qiankun会为承载子应用的container元素创建一个 Shadow DOM。子应用的所有 DOM 节点和<style>标签都会被添加到这个 Shadow DOM 内部。 - 效果 :Shadow DOM 提供了一个完全封装的"影子"文档树。内部的样式规则绝对不会 泄露到外部主文档,外部的全局样式也绝对不会影响到内部。
如何开启
在主应用注册子应用时,设置 sandbox 配置项:
main-app/src/qiankun-setup.js
js
const apps = [
{
name: 'sub-app-vue',
entry: '//localhost:8081',
container: '#subapp-container',
activeRule: '/vue-app',
props: {
// 在这里向子应用传递参数
}
},
// ... 其他应用
];
export const setupQiankun = () => {
registerMicroApps(apps);
start({
sandbox: {
strictStyleIsolation: true // 此处开启 Shadow DOM
}
});
};
验证 Shadow DOM
开启 strictStyleIsolation: true 后,重复上面的实验:
- 启动主应用,按钮是绿色。
- 切换到 Vue 子应用,子应用的按钮是橙色圆角。
- 此时,主应用的按钮依然是绿色,未受任何影响。
打开开发者工具,你会看到 subapp-container 内部结构发生了变化:
html
<div id="subapp-container">
#shadow-root (open)
|-- <!-- 子应用的所有内容和样式都在这里 -->
|-- <style> .common-button { ... } </style>
|-- <div id="app">
| <button class="common-button">Vue子应用按钮</button>
| </div>
</div>

结论:Shadow DOM 提供了完美的样式隔离。
缺点:
- 兼容性:一些旧浏览器不支持 Shadow DOM。
- 弹出层问题 :子应用中的
modal、dialog等需要挂载到document.body的组件,其 DOM 会被渲染到 Shadow DOM 之外,导致无法应用 Shadow DOM 内部的样式。你需要额外处理这类组件的挂载节点。 - 事件冒泡:某些事件可能无法穿透 Shadow DOM 边界,需要手动处理。
3. 作用域沙箱(Scoped CSS)
这是 qiankun 提供的另一种手动开启的折中方案。它不像 Shadow DOM 那样彻底,但能解决大部分问题且没有 Shadow DOM 的那些缺点。
- 原理 :
qiankun会在运行时,为子应用动态添加的每个样式规则,自动增加一个基于data-qiankun属性的前缀选择器。 - 效果 :例如,子应用的规则
.common-button { color: orange; }会被改写成div[data-qiankun="sub-app-vue"] .common-button { color: orange; }。qiankun会确保子应用的容器div上有data-qiankun="sub-app-vue"这个属性。这样,样式规则就只对该子应用容器内的元素生效了。
如何开启
关闭 Shadow DOM(experimentalStyleIsolation: false 或不设置),然后在 start 函数中配置 sandbox:
main-app/src/qiankun-setup.js
js
export const setupQiankun = () => {
registerMicroApps(apps);
start({
sandbox: {
experimentalStyleIsolation: true, // 这里开启
}
});
}
验证 Scoped CSS
使用上述配置,再次进行实验:
- 启动主应用,按钮是绿色。
- 切换到 Vue 子应用,子应用的按钮是橙色圆角。
- 主应用的按钮依然是绿色。
打开开发者工具,检查 Vue 子应用的样式:
-
子应用的容器
div会被添加一个属性:<div id="subapp-container" data-qiankun="sub-app-vue">...</div> -
在
<head>中,你会找到被qiankun改写后的样式:cssdiv[data-qiankun="sub-app-vue"] .common-button { background-color: #f0ad4e; border-radius: 8px; }


结论:Scoped CSS 通过动态添加属性选择器,实现了子应用样式的作用域限制,成功隔离了主应用和子应用、以及子应用之间的样式,且没有 Shadow DOM 的那些副作用。
缺点:
- 性能开销:相比默认策略,它需要遍历和改写所有 CSS 规则,有轻微的性能开销。
- 复杂选择器:在极少数情况下,对于非常复杂的 CSS 选择器,改写可能会失败或产生非预期的效果。
八. 应用间常用通信方法
前两种基本够用,但有局限性 最好了解每种通信方式的优劣
1. 基于 initGlobalState 的官方通信方案 (推荐)
这是 qiankun 官方推荐并内置的通信机制,基于 发布-订阅模式 实现,适用于主子应用、子子应用间的复杂状态同步。
- 原理 :
qiankun在内部维护一个全局状态对象。主应用通过initGlobalState初始化这个状态,并返回一个actions对象。子应用在mount生命周期钩子中通过 props 获取这个actions对象,然后可以使用onGlobalStateChange监听状态变化,或使用setGlobalState修改状态。任何一方的修改都会通知所有监听者。
使用步骤
- 主应用:初始化和修改状态
main-app/src/qiankun-setup.js
js
import { initGlobalState, registerMicroApps, start } from 'qiankun';
// 1. 定义一个初始 state
const initialState = {
theme: "light",
userInfo: {
name: "Admin",
role: "administrator",
},
event: null,
};
// 2. 初始化 state,返回 actions 对象
const actions = initGlobalState(initialState);
// 3. 监听 state 变化,并做出响应
actions.onGlobalStateChange((state, prev) => {
// state: 当前最新的 state
// prev: 变化前的 state
console.log('【主应用】监听到全局状态变化:', state);
});
// 注册和启动 qiankun
export const setupQiankun = () => {
registerMicroApps(/*...*/);
start();
};
- 子应用:监听和修改状态
sub-app-vue/src/main.js
js
// ...
let instance = null;
function render(props) {
const { container } = props;
// 将 actions 保存下来,方便其他组件使用
// 方案一:挂载到全局 (简单但不推荐)
// app.config.globalProperties.$actions = actions;
// 方案二:通过 provide/inject (推荐)
instance = createApp(App);
if (actions) {
instance.provide("actions", actions);
}
instance.use(router).mount(container ? container.querySelector('#app') : '#app');
}
// ...
export async function mount(props) {
console.log('[vue] props from main framework', props);
// 1. 在 mount 时注册监听
props.onGlobalStateChange((state, prev) => {
console.log('[vue-sub] 监听到全局状态变化:', state);
// 更新子应用自己的状态...
// 例如,如果子应用也有主题设置,可以根据全局 appTheme 来更新
if (state.appTheme !== prev.appTheme) {
// 执行子应用内部的主题切换逻辑
}
});
render(props);
}
// ...
- 子应用组件内使用
sub-app-vue/src/views/HomeView.vue
html
<template>
<div>
<button @click="changeGlobalState">从Vue子应用修改全局状态</button>
<button @click="toggleTheme">从Vue子应用切换主题</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, inject } from "vue";
// 通过 inject 获取全局 actions
const actions = inject("actions");
const theme = ref("");
// 监听全局状态变化的处理器
const stateChangeHandler = (state, prevState) => {
console.log("【Vue子应用】监听到全局状态变化:", state);
theme.value = state?.theme;
};
onMounted(() => {
// 组件挂载时,注册 onGlobalStateChange 监听
if (actions) {
// console.log("【Vue子应用】注册全局状态监听", actions);
// 初始化时,先主动获取一次当前 state
const currentState = actions.getGlobalState?.();
theme.value = currentState?.theme;
actions.onGlobalStateChange(stateChangeHandler, true); // 第二个参数为 true,立即执行一次
}
});
onUnmounted(() => {
// 组件卸载时,注销监听
if (actions) {
actions.offGlobalStateChange(stateChangeHandler);
}
});
</script>
看效果:

2. 基于 props 的单向通信
这是最简单的通信方式,用于主应用向子应用单向传递数据或方法。
- 原理 :在主应用
registerMicroApps时,可以通过props属性传递任意数据或函数给子应用。子应用在mount生命周期钩子中,通过参数props接收这些数据。
使用步骤
- 主应用:通过
props传递数据和函数main-app/src/qiankun-setup.js
js
function showAlert(msg) {
alert(`[主应用消息]: ${msg}`);
}
const apps = [
{
name: 'sub-app-react',
entry: '//localhost:3000',
container: '#subapp-container',
activeRule: '/react-app',
props: {
// 传递静态数据
mainAppName: 'MyMainApp',
// 传递方法,让子应用可以调用主应用的能力
showAlert,
// 传递一个对象,子应用可以根据需要解构
config: {
apiBaseUrl: '/api',
// ...
}
}
},
// ...
];
export const setupQiankun = () => {
registerMicroApps(apps);
start();
};
- 子应用:在
mount时接收和使用sub-app-react/src/index.js
js
// ...
function render(props) {
const { container, mainAppName, showAlert, config } = props;
// 可以在这里将 props 传递给根组件
const dom = container ? container.querySelector('#root') : document.getElementById('root');
root = ReactDOM.createRoot(dom);
root.render(
<React.StrictMode>
<App mainAppName={mainAppName} showAlert={showAlert} config={config} />
</React.StrictMode>
);
}
// ...
export async function mount(props) {
console.log('[react] props from main framework', props);
render(props);
}
// ...
- 子应用组件内使用
sub-app-react/src/App.js
js
function App({ mainAppName, showAlert, config }) {
const handleClick = () => {
// 调用从主应用传来的方法
showAlert(`Hello from ${mainAppName}'s React child! API URL: ${config.apiBaseUrl}`);
};
return (
<div>
<button onClick={handleClick}>调用主应用方法并显示配置</button>
</div>
);
}
3. 基于路由参数或路径
这是最简单、最通用的 Web 页面间通信方式。
- 原理 :将需要传递的信息编码到 URL 中。可以是 query string (
?id=123),也可以是 path (/user/123)。主应用通过修改 URL 来"通知"子应用,子应用监听 URL 变化并解析参数来获取信息。
使用步骤
- 主应用:通过路由导航传递参数
main-app/src/App.vue
html
<template>
<div>
<!-- ... -->
<router-link to="/vue-app?userId=1001&userName=Alice">访问Vue子应用并传参</router-link> |
<router-link to="/react-app/detail/2002">访问React子应用并传路径参数</router-link>
</div>
</template>
- 子应用:在组件内解析路由参数
sub-app-vue/src/views/HomeView.vue
html
<script setup>
import { useRoute } from 'vue-router';
import { onMounted } from 'vue';
const route = useRoute();
onMounted(() => {
const userId = route.query.userId;
const userName = route.query.userName;
console.log('【Vue子应用】从 URL 获取到的 userId:', userId, 'userName:', userName); // "1001", "Alice"
});
</script>
sub-app-react/src/App.js (假设React路由已配置 '/detail/:id')
js
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom'; // 假设使用 react-router-dom
function App() {
const { id } = useParams();
useEffect(() => {
console.log('【React子应用】从 URL 获取到的路径参数 id:', id); // "2002"
}, [id]);
return (
<div>
<h3>React子应用</h3>
{/* ... */}
</div>
);
}
4. 基于 localStorage / sessionStorage
通过浏览器提供的本地存储机制,实现不同应用间的数据共享。
- 原理 :一个应用将数据存入
localStorage,另一个应用从localStorage读取数据。由于localStorage具备同源共享特性,因此在同一个主域名下,主应用和所有子应用都能访问到相同的数据。
使用步骤
- 发送方(例如主应用):存储数据
main-app/src/App.vue
html
<template>
<button @click="saveDataToLocalStorage">主应用存储数据到 localStorage</button>
</template>
<script setup>
function saveDataToLocalStorage() {
localStorage.setItem('shared_data_from_main', JSON.stringify({ userId: 'main_user_001', role: 'admin' }));
console.log('【主应用】数据已存储到 localStorage');
// 可以触发一个事件通知其他应用数据已更新
window.dispatchEvent(new CustomEvent('localStorageUpdate', { detail: { key: 'shared_data_from_main' } }));
}
</script>
- 接收方(例如 Vue 子应用):读取数据
sub-app-vue/src/views/HomeView.vue
html
<template>
<div>
<button @click="readDataFromLocalStorage">Vue子应用读取 localStorage</button>
<p v-if="sharedData">从 localStorage 读取到的数据: {{ sharedData }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const sharedData = ref(null);
function readDataFromLocalStorage() {
const data = localStorage.getItem('shared_data_from_main');
sharedData.value = data ? JSON.parse(data) : null;
console.log('【Vue子应用】读取到 localStorage 数据:', sharedData.value);
}
// 为了实现响应式,监听 localStorage 的变化(非所有浏览器都支持 storage 事件监听所有 key)
// 更好的做法是结合 CustomEvent
const handleStorageUpdate = (event) => {
if (event.detail && event.detail.key === 'shared_data_from_main') {
readDataFromLocalStorage();
}
};
onMounted(() => {
readDataFromLocalStorage(); // 首次加载时读取
window.addEventListener('localStorageUpdate', handleStorageUpdate);
});
onUnmounted(() => {
window.removeEventListener('localStorageUpdate', handleStorageUpdate);
});
</script>
5. 基于状态管理工具 (Pinia/Vuex, Redux, Zustand等)
如果所有子应用都使用相同的前端框架(例如都是 Vue),并且都使用了相同的状态管理库,那么可以考虑使用一个共享的状态管理实例。
- 原理 :在主应用中初始化一个状态管理实例,并通过
props或initGlobalState将其传递给子应用。子应用接收后,可以直接使用这个共享的实例来读写状态。
使用步骤
- 主应用:初始化状态管理实例并传递 (以 Vue/Pinia 为例)
main-app/src/main.js
js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { setupQiankun } from './qiankun-setup';
import { createPinia } from 'pinia';
const app = createApp(App);
const pinia = createPinia(); // 创建一个 Pinia 实例
app.use(router).use(pinia).mount('#app');
// 将 Pinia 实例或特定的 Store 传递给 qiankun
setupQiankun(pinia); // 修改 qiankun-setup.js,使其能接收 pinia 实例
main-app/src/qiankun-setup.js
js
// ...
export const setupQiankun = (mainPiniaInstance) => { // 接收 pinia 实例
registerMicroApps(apps.map(app => ({
...app,
props: {
...app.props,
mainPiniaInstance, // 将主应用的 Pinia 实例作为 props 传递
},
})));
start();
};
- 子应用:接收并使用共享实例
sub-app-vue/src/main.js
js
// ...
import { createPinia } from 'pinia'; // 子应用也需要 Pinia
let instance = null;
let piniaInstance = null; // 存储 Pinia 实例
function render(props) {
const { container, mainPiniaInstance } = props;
instance = createApp(App);
if (mainPiniaInstance) {
// 如果主应用传递了 Pinia 实例,则使用它
piniaInstance = mainPiniaInstance;
} else {
// 否则,子应用自己创建一个(独立运行或非 Vue 主应用时)
piniaInstance = createPinia();
}
instance.use(piniaInstance).use(router).mount(container ? container.querySelector('#app') : '#app');
}
// ...
- 子应用组件内使用共享 Store
sub-app-vue/src/components/MyComponent.vue
html
<template>
<div>
<p>共享计数器: {{ sharedCounter }}</p>
<button @click="incrementSharedCounter">增加共享计数器</button>
</div>
</template>
<script setup>
import { useSharedStore } from './stores/sharedStore'; // 假设定义了一个共享的 store
const sharedStore = useSharedStore();
const sharedCounter = computed(() => sharedStore.counter);
function incrementSharedCounter() {
sharedStore.increment();
}
</script>
结论:此方案适用于技术栈同构的场景。它能提供非常细粒度的状态管理和响应性。缺点是强耦合于特定框架的状态管理库,限制了技术栈异构的优势。
推荐优先级
根据实践经验,通信方案的选择应遵循以下优先级:
initGlobalState(最高优先级) :官方方案,功能强大且规范,适用于所有需要双向、响应式数据同步的场景。是构建健壮微前端通信系统的首选。props:适用于主应用向子应用进行初始化和能力注入的场景,简单明了,职责清晰。- URL 参数:适用于传递与页面定位相关的简单状态,符合 Web 标准,对用户友好。
BroadcastChannel/SharedWorker:适用于跨多个 Tab 或需要独立线程处理的复杂通信场景,提供更强大的解耦和性能优势,但实现相对复杂。localStorage/sessionStorage:适用于存储不频繁变动且需要持久化的简单数据,通常需要配合事件机制来通知变化。状态管理工具(Pinia/Vuex, Redux等) :适用于技术栈同构的场景,提供细粒度的响应式状态管理,但牺牲了技术栈异构的灵活性。