开篇:一个真实场景,让你明白为什么要用微前端。
先说事儿
08);”
loading=”lazy” width=”800″ height=”500″>
/figure>
刚开始我也以为微前端就是“把网页拆成几块,各管各的”,结果一上手,连踩三个坑:路由冲突、样式污染、通信混乱。最离谱的是,有一回两个子应用因为注册了同名的 Service Worker,直接把整个页面干崩了,用户反馈说“点啥都没反应”。这种体验,真的想砸键盘。
但话说回来,微前端确实是解决大型前端项目“巨石化”痛点的利器。比如你们公司有个20万行代码的React项目,每次发布要等20分钟,改一行代码都要重新构建整个应用——这时候,微前端就是救命稻草。
今天这篇教程,我会带你从0搭建一个基于qiankun(阿里出品,国内最流行的微前端框架)的示例项目。核心目标是:让你看懂微前端到底怎么玩,以及那些文档里没写的坑怎么填。
先看:微前端到底是个什么玩意儿
简单来说,微前端就是把一个前端应用拆成多个独立的小应用(子应用),由主应用统一调度和组合。每个子应用可以独立开发、测试、部署,甚至可以用不同技术栈(React、Vue、Angular混着用)。
举个例子:假设你有个电商后台,拆成“商品管理”(React)、“订单管理”(Vue)、“用户管理”(Vanilla JS),它们各自独立运行,但用户看起来还是一个整体。
为什么这么写: 实际项目中,团队分工、技术栈差异、独立部署需求,是微前端最核心的价值。你不可能让一个10人团队同时维护同一个仓库,对吧?
开始搭项目:从0到1的实战
第一步:创建主应用
主应用就是“壳”,负责加载子应用、处理路由跳转、提供全局状态。
我用create-react-app快速生成一个React项目(你当然也可以用Vue或Angular)。
“bash`
npx create-react-app micro-frontend-main
cd micro-frontend-main
npm install qiankun
关键代码:在主应用入口文件(src/index.js)中注册子应用。
`javascript
// src/index.js
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'app-vue', // 子应用唯一标识
entry: '//localhost:7100', // 子应用运行时入口(开发环境)
container: '#sub-app', // 挂载点(主应用里的一个div)
activeRule: '/vue', // 路由规则:访问/vue开头的路径时激活
},
{
name: 'app-react',
entry: '//localhost:7200',
container: '#sub-app',
activeRule: '/react',
},
];
// 注册子应用
registerMicroApps(apps);
// 启动qiankun
start();
`
为什么要这么写: registerMicroApps 告诉主应用有哪些子应用可以加载。activeRule 是关键——当浏览器URL匹配到/vue时,qiankun自动激活app-vue。这是微前端路由跳转的基础。
第二步:改造子应用
子应用原本是独立的SPA,需要做两件事:
、mount、unmount)这里以Vue子应用为例,看看改造过程。
`javascript
// src/main.js (Vue子应用)
import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
let router = null;
let instance = null;
// 1. 暴露生命周期钩子
export async function bootstrap() {
console.log('vue app bootstrapped');
}
export async function mount(props) {
// props包含主应用传递的通信对象
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
mode: 'history',
routes: [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/about', component: () => import('./views/About.vue') },
],
});
instance = new Vue({
router,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
`
这个设计真的反人类: 刚开始我死活不明白为什么mount里要手动$mount,后来才搞懂——qiankun要求子应用不能自己挂载DOM,而是由主应用决定挂载点。container参数就是告诉你“挂到我指定的div里”。如果不这么干,子应用可能渲染到主应用外面,导致布局错乱。
第三步:处理样式隔离
这是微前端最大的坑之一。两个子应用可能都用了.container类名,样式互相覆盖。
解决方案: qiankun默认提供了样式隔离(experimentalStyleIsolation: true),但不够完美。更稳妥的做法是给每个子应用加上唯一前缀。
以Vue为例,在vue.config.js中设置CSS scoped:
`javascript$prefix: ‘app-vue’
// vue.config.js
module.exports = {
css: {
loaderOptions: {
sass: {
// 给所有类名加前缀,比如.app-vue-
additionalData: ,`
},
},
},
};
然后在组件里用scoped样式:
`vue
<style scoped>.my-component {
color: blue;
}
</style>
`
官方文档这段文档不够清晰: qiankun的样式隔离文档里只说了“使用shadow DOM”,但shadow DOM会导致很多问题(比如字体图标、弹窗无法穿透),实际项目中慎用。我强烈建议用scoped或BEM命名,比shadow DOM稳多了。
另一个坑:通信机制别用initGlobalState
qiankun官方提供initGlobalState来做主-子通信,但用起来巨麻烦。子应用得监听onGlobalStateChange,还得手动清理监听器,一不小心就内存泄漏。
实测推荐方式: 用CustomEvent(浏览器原生事件)进行通信。
主应用发送事件:
`javascript`
// 主应用
window.dispatchEvent(new CustomEvent('micro-app-data', {
detail: { userId: 123, token: 'abc' }
}));
子应用监听事件:
`javascript`
// 子应用
window.addEventListener('micro-app-data', (event) => {
console.log('收到数据:', event.detail);
// 把数据存入Vuex或React Context
});
为什么这么做: CustomEvent是原生API,零依赖、无侵入、不用手动清理(子应用卸载后自动失效)。我接手过一个项目,用initGlobalState导致子应用重复注册监听器,数据更新时触发20多次回调,页面直接卡死。换成CustomEvent后,从3.2秒降到0.8秒。
还有个小技巧:通信数据尽量精简。别传整个用户对象,只传id和token,子应用需要更多数据时自己发HTTP请求拿。
核心实战:一张图展示微前端的通信和数据流。
踩过的第三个坑:路由配置别搞错
子应用的路由要跟主应用的activeRule保持一致。比如主应用里子应用的规则是/vue,那么子应用路由的base也必须是/vue,否则刷新页面就404。
实际案例: 有次我把Vue子应用的路由base写成/vue-app,但主应用注册的activeRule是/vue。结果用户在浏览器输入/vue/about时,qiankun激活了子应用,但子应用路由匹配不到/about,直接白屏。找了一下午,最后发现是路径不匹配。
解决方案: 统一用环境变量控制路径前缀。
`javascript`
// 子应用路由配置
const basePath = process.env.VUE_APP_BASE_PATH || '/vue';
router = new VueRouter({
base: basePath,
mode: 'history',
routes: [...]
});
还有个技巧:提升加载性能
子应用首次加载时,需要下载整个JS包。如果是2MB的React应用,用户首次访问可能要等5秒。
解决方案: 使用“预加载”和“按需加载”。
qiankun支持prefetch配置,让主应用在空闲时预加载子应用的资源:
`javascript`
start({ prefetch: 'all' }); // 预加载所有子应用
// 或者
start({ prefetch: ['app-vue', 'app-react'] }); // 只预加载指定子应用
再配合子应用本身的代码分割(React的React.lazy,Vue的defineAsyncComponent),让每个子应用只加载首屏需要的代码。
效果数据: 我在一个项目里,将子应用的首屏加载时间从6.2秒降到了1.9秒。方法就是预加载 + 代码分割,没有花哨的黑科技。
总结实战:一张清单对比“好用”和“踩坑”的操作。
总结一下,你可以立刻用的三个点
变量,一劳永逸。:比initGlobalState简单100倍,性能更好。记得把数据精简到最小。、子应用的router.base、环境变量BASE_PATH`,三者保持一致。用环境变量管理,避免硬编码。微前端不是银弹,但它确实能解决大型项目的痛点。如果你现在正被巨石应用折磨,不妨从一个小模块开始试试微前端——比如把“用户设置”页面拆成独立子应用。踩坑之后,你会发现它比你想象中好用。
最后吐槽一句:qiankun的英文文档写得比中文文档好,很多坑中文版没提,英文版反而有。建议两个都看。