✨ 微前端实战:React 主应用 + Vue 子应用
微前端架构将大型应用拆解为独立开发、部署的子应用,极大提升了复杂系统的可维护性。本文将手把手教你使用 Qiankun 框架,在 Vite 构建的 React 18 主应用中集成 Vue 3 子应用。
🛠️ 一、主应用 (React 18 + Vite):搭好舞台
主应用就像个"大舞台",负责调度和管理各个"演员"(子应用)。
-
请来"管家" Qiankun
bashnpm install qiankun --save # 或者 yarn add qiankun
-
登记"演员"信息 & 启动舞台 在 React 主应用的启动文件(通常是
main.jsx
或App.jsx
)里操作:javascriptimport { registerMicroApps, start } from 'qiankun'; // 告诉 Qiankun 有哪些子应用可以上台表演 registerMicroApps([ { name: 'vue3-module', // 给子应用起个唯一艺名 entry: '//localhost:5500', // 子应用开发时在哪候场(开发服务器地址) container: '#vue-app-container', // 舞台上哪个区域给这个演员(DOM元素ID) activeRule: '/vue-module', // 什么信号(路由)一响,就该他上场了 props: { // 给演员的初始道具(数据) greeting: 'Hello from React Boss!', userRole: 'admin', }, }, ]); // 灯光师、音响师就位,舞台启动! start({ sandbox: { experimentalStyleIsolation: true, // 重要!给演员单独化妆间(样式隔离),避免串妆 }, });
💡 贴心提示:
activeRule
: 主应用的路由(比如用了react-router-dom
)匹配到这个路径时,就召唤对应的子应用。experimentalStyleIsolation: true
: 强烈建议开启。这相当于给子应用套了个"透明玻璃房"(Shadow DOM),它们的 CSS 就不会跑出来把主应用或者其他子应用的页面搞乱。
-
给演员留好位置 (路由配置 - React Router v6 示例) 确保主路由知道把
/vue-module
开头的请求交给子应用:javascriptimport { BrowserRouter, Routes, Route } from 'react-router-dom'; function MainApp() { return ( <BrowserRouter> <Routes> <Route path="/" element={<HomePage />} /> {/* 这里就是留给 Vue 子应用的专属区域! */} <Route path="/vue-module/*" element={<div id="vue-app-container" />} /> </Routes> </BrowserRouter> ); }
🎭 二、子应用 (Vue 3 + Vite):演员准备登场
Vue 子应用是个"独立演员",但要学会在 Qiankun 的舞台上表演。
-
装上"舞台适配器"
bashnpm install vite-plugin-qiankun --save-dev # 或者 yarn add vite-plugin-qiankun -D
-
配置"化妆间"(
vite.config.js
)javascriptimport { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import qiankun from 'vite-plugin-qiankun'; // 引入适配器 export default defineConfig({ plugins: [ vue(), qiankun('vue3-module', { // ⚠️ 艺名必须和主应用登记的一模一样! useDevMode: true // 开发模式也要能上台排练 }) ], server: { port: 5500, // ⚠️ 候场地址端口,必须和主应用 `entry` 一致 origin: 'http://localhost:5500' // 确保资源能找到回家的路 }, base: process.env.NODE_ENV === 'production' ? '/path/to/your/vue-subapp/' // 正式演出时的后台路径 : '/', // 排练时就在后台门口 });
🚨 关键警报:
qiankun('vue3-module', ...)
: 这里的'vue3-module'
必须、必须、必须 和主应用registerMicroApps
里的name
完全一致!不然对不上号。base
: 这是最容易栽跟头的地方! 开发环境设为'/'
,生产环境必须是你子应用最终部署在服务器上的相对路径(相对于主应用的域名)。配错了,图片、JS、CSS 全找不到!
-
学会"登台谢幕"(改造
main.js
) 让 Vue 应用知道自己是独立演出还是在 Qiankun 的大舞台上:javascriptimport { createApp } from 'vue'; import App from './App.vue'; import router from './router'; // 子应用自己的路由 import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'; let vueApp = null; // 保存 Vue 应用实例 // 独立表演时的剧本 function renderStandaloneApp(container = null) { vueApp = createApp(App); vueApp.use(router); // 挂载自己的路由、状态管理等 // ... 其他插件 vueApp.mount(container ? container.querySelector('#app') : '#app'); // 挂载到指定或默认位置 } // 判断:现在是独立演出吗? if (!qiankunWindow.__POWERED_BY_QIANKUN__) { renderStandaloneApp(); // 是,按独立方式启动 } // Qiankun 舞台的生命周期钩子:演员要遵守舞台规则 renderWithQiankun({ // 舞台导演喊"上场!" mount(props) { console.log('🎬 Vue子应用收到道具:', props); // 能看到主应用给的道具(props) renderStandaloneApp(props.container); // 把自己挂载到舞台导演指定的容器里 // 通常这里也会初始化通信监听 }, // 第一次上场前准备 (可空) bootstrap() { console.log('🎬 Vue子应用准备上场...'); }, // 导演要求临时调整表演 (可空) update(props) { console.log('🎬 Vue子应用收到新剧本:', props); }, // 导演喊"下场!" unmount() { console.log('🎬 Vue子应用谢幕...'); vueApp?.unmount(); // 把自己卸载干净 vueApp = null; // 清空实例 }, });
🎭 演员修养:
qiankunWindow
: 用它判断是在独立环境还是 Qiankun 舞台环境。renderWithQiankun
: 告诉 Qiankun 导演:"这是我的上场(mount
)、下场(unmount
)方法,您随时调用"。mount(props)
:props.container
是导演递过来的话筒支架(挂载点 DOM 元素),props
里还有导演给的道具和通信工具。- 资源路径: 再次强调
base
配置正确的重要性!所有相对路径资源(./assets/logo.png
)都依赖它。
📣 三、主应用和子应用"对讲机":通信
舞台上的演员需要交流。Qiankun 提供了 initGlobalState
这个"对讲机系统"。
-
主应用:安装并管理对讲机
javascript// 主应用某处 (e.g., src/utils/qiankunState.js) import { initGlobalState } from 'qiankun'; // 初始化对讲机公共频道和默认消息 const initialState = { theme: 'light-mode', currentProjectId: 123, notifications: [] }; const globalActions = initGlobalState(initialState); // 主应用自己也要听听公共频道 globalActions.onGlobalStateChange((newState, oldState) => { console.log('主应用监听到:', newState, '之前是:', oldState); // 根据消息更新主应用自己的状态或UI (比如换主题) }); // 主应用发广播 (e.g., 用户切换了主题) function switchAppTheme(newTheme) { globalActions.setGlobalState({ ...globalActions.getGlobalState(), // 先拿当前所有状态 theme: newTheme // 只更新 theme 字段 }); } // 把 globalActions 导出去,哪里需要发广播哪里引入 export { globalActions };
-
子应用:加入公共频道,收听和发言 在子应用
mount
时拿到对讲机:javascriptrenderWithQiankun({ mount(props) { // ... 挂载逻辑 // 1. 领一个对讲机耳机,收听公共频道 props.onGlobalStateChange( (newState, oldState) => { console.log('👂 Vue子应用听到:', newState); // 根据消息更新子应用内部状态 (比如用 Pinia/Vuex 存 theme) if (newState.theme !== oldState?.theme) { switchThemeLocally(newState.theme); // 本地切换主题 } }, true // true 表示立刻用当前状态触发一次上面的回调 ); // 2. 子应用也可以发广播 (但只能更新主应用定义好的那些"话题") function updateProjectStatus(status) { props.setGlobalState({ currentProjectStatus: status // 只能更新 initialState 里已有的一级属性! }); } }, // ... 其他生命周期 });
📡 通信规则:
- 主控权在主应用: 主应用初始化状态和对讲机系统。
- 子应用"有限发言权": 子应用只能修改 全局状态对象中主应用已经预先定义好 的一级属性(比如
theme
,currentProjectId
)。它不能自己发明一个新的一级属性广播出去。 - 记得"摘耳机": Qiankun 在子应用下场(
unmount
)时会自动帮它关掉监听(offGlobalStateChange
),一般无需子应用自己操作。
四、必看注意事项
- 名称一致性 :主/子应用的
name
必须完全匹配 - base 配置:子应用生产环境需设置正确部署路径
- 样式隔离 :始终开启
experimentalStyleIsolation
- 路由冲突:确保主应用路由不拦截子应用路径
- 部署优化:生产环境需配置 CDN 和 CORS