4、主应用搭建:创建React主应用、配置qiankun、注册子应用、全局状态管理
好,咱们进入实战环节。
前面聊了那么多微前端的概念和架构选型,说白了都是为了这一刻。今天我们就亲手搭一个主应用出来。我自己的习惯是,先把主应用跑起来,再慢慢往里填子应用。这样心里踏实。
4.1 创建React主应用
先别想太复杂。主应用本质上就是一个普通的React项目。我用的是Create React App,当然你用Vite也行,看团队习惯。
npx create-react-app micro-main-app
cd micro-main-app
npm start
嗯,这里要注意一点:主应用不需要太重的UI框架。它就是个容器,负责加载子应用、管理路由、做全局状态。你想想看,如果主应用本身又大又重,那微前端带来的性能优势就大打折扣了。
我个人习惯把主应用的代码控制在500行以内。超过这个数,就该考虑是不是把某些逻辑下沉到子应用里去了。
4.2 安装并配置qiankun
qiankun是蚂蚁开源的一个微前端框架。说实话,它把很多复杂的东西都封装好了。你只需要关心两件事:注册子应用和生命周期管理。
安装很简单:
npm install qiankun --save
然后在主应用的入口文件里,我们来做配置。我直接贴代码,你照着抄就行:
// src/index.js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react-app', // 子应用名称,唯一
entry: '//localhost:3001', // 子应用入口地址
container: '#sub-app-container', // 挂载点
activeRule: '/react-app', // 路由匹配规则
},
{
name: 'vue-app',
entry: '//localhost:3002',
container: '#sub-app-container',
activeRule: '/vue-app',
},
]);
start();
这里有个坑,我当年踩过。子应用的entry地址,不要写成相对路径。qiankun内部是通过fetch去拉取子应用的HTML的,相对路径在跨域场景下会出问题。我建议直接用完整的URL,包括端口号。
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
}
4.3 注册子应用的生命周期
光注册还不够。子应用需要暴露三个生命周期钩子:bootstrap、mount、unmount。qiankun会在合适的时机调用它们。
以React子应用为例,你需要在子应用的入口文件里加上:
// 子应用的 src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
function render(props) {
const { container } = props;
ReactDOM.render(
<App />,
container ? container.querySelector('#root') : document.getElementById('root')
);
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('子应用启动');
}
export async function mount(props) {
render(props);
}
export async function unmount(props) {
const { container } = props;
ReactDOM.unmountComponentAtNode(
container ? container.querySelector('#root') : document.getElementById('root')
);
}
为什么要有__POWERED_BY_QIANKUN__这个判断?因为子应用既要能独立运行,也要能嵌入主应用。这个变量就是qiankun注入的标识。我在项目中遇到过,有人忘了加这个判断,结果子应用独立运行时白屏了。排查了半天,发现是生命周期函数没被调用。
4.4 全局状态管理
微前端最头疼的问题之一:主应用和子应用怎么共享状态?
qiankun提供了initGlobalState方法。说白了,就是主应用维护一个全局的store,子应用通过props拿到它。
主应用里这样写:
import { initGlobalState } from 'qiankun';
const initialState = {
user: { name: '张三', role: 'admin' },
token: 'abc123',
};
const actions = initGlobalState(initialState);
// 监听状态变化
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态变了', state, prev);
});
// 更新状态
actions.setGlobalState({ user: { name: '李四' } });
子应用通过props接收:
export async function mount(props) {
// props.onGlobalStateChange 监听
// props.setGlobalState 更新
render(props);
}
4.5 路由整合
主应用和子应用的路由怎么配合?这是另一个容易出问题的地方。
我常用的做法是:主应用用BrowserRouter,子应用用HashRouter。为什么?因为BrowserRouter需要服务端配合,子应用嵌入后路径容易冲突。HashRouter用#号隔离,省心。
主应用的路由配置:
// 主应用的路由
<BrowserRouter>
<Switch>
<Route path="/react-app" component={MicroAppWrapper} />
<Route path="/vue-app" component={MicroAppWrapper} />
<Route path="/" exact component={Home} />
</Switch>
</BrowserRouter>
子应用的路由:
// 子应用的路由,用HashRouter
<HashRouter>
<Switch>
<Route path="/" exact component={Dashboard} />
<Route path="/detail" component={Detail} />
</Switch>
</HashRouter>
这样,主应用访问/react-app时,加载子应用。子应用内部跳转/detail,实际URL是/react-app#/detail。互不干扰。
4.6 避坑指南
最后,分享几个我踩过的坑:
- 样式隔离:qiankun默认用Shadow DOM做样式隔离,但有些老项目不支持。我建议子应用用CSS Modules或BEM命名规范,从源头避免冲突。
- 全局变量污染:子应用卸载后,要清理全局事件监听、定时器。否则内存泄漏。我曾在生产环境遇到过,子应用切走了,但setInterval还在跑,CPU直接飙到100%。
- 子应用加载慢:第一次加载子应用时,qiankun会去fetch整个HTML。如果子应用很大,会有明显的白屏。解决方案是用prefetch预加载,或者加一个loading动画。
总结一下:
- 主应用要轻量,只做容器和路由
- qiankun配置时注意跨域和entry地址
- 子应用必须暴露生命周期钩子
- 全局状态只放共享数据,别滥用
- 路由用HashRouter隔离,省心
好了,主应用搭建这块就聊到这。下一章我们讲子应用的开发和接入,到时候会手把手带你写一个React子应用和一个Vue子应用。有什么问题,欢迎在评论区交流。