3、qiankun 框架入门:核心概念、通信机制与生命周期
好,咱们今天来聊聊 qiankun。说实话,微前端框架里,qiankun 算是国内用得最广的一个了。我最早接触它是在 2020 年,当时公司要重构一个老旧的 ERP 系统,几十个模块耦合在一起,改一个地方崩一片。嗯,那会儿我就想,要是能拆开独立部署就好了。qiankun 正好解决了这个问题。
3.1 核心概念:微前端的基础设施
qiankun 的核心思想很简单——它不要求你重写应用,而是让你把现有的 SPA(单页应用)直接“塞”进一个容器里。说白了,它就是一个微前端的“操作系统”。
我个人习惯把 qiankun 的核心概念拆成三块:
- 主应用(Base App):负责加载、调度、管理子应用。它本身也是一个前端应用,通常是一个空壳子,只包含导航、布局这些公共部分。
- 子应用(Micro App):独立开发、独立部署的业务模块。每个子应用可以有自己的路由、状态管理、构建工具。
- 沙箱(Sandbox):qiankun 最核心的机制之一。它通过 Proxy 拦截全局变量,确保子应用之间不会互相污染。比如子应用 A 改了 window.name,子应用 B 完全感知不到。
关键点:qiankun 是基于 single-spa 封装的,但它比 single-spa 多了沙箱、样式隔离、资源预加载这些开箱即用的能力。如果你用过 single-spa,你会发现 qiankun 的配置量少了很多。
3.2 主应用与子应用的通信机制
微前端架构里,通信是个绕不开的话题。你想想看,主应用要告诉子应用“用户登录了”,子应用要告诉主应用“我这边有个操作完成了”。怎么搞?
qiankun 提供了两种官方通信方式:
3.2.1 基于 props 的通信(推荐)
这是最直接的方式。主应用在加载子应用时,通过 props 把数据传进去。子应用在生命周期钩子里接收。
我在项目中遇到过一个问题:主应用和子应用都需要共享用户信息。如果每次都用全局事件,代码会变得很难维护。后来我统一用 props 传递,清晰多了。
主应用代码示例:
// 主应用注册子应用时传递 props
registerMicroApps([
{
name: 'app1',
entry: '//localhost:7101',
container: '#subapp-container',
activeRule: '/app1',
props: {
userInfo: { name: '张三', role: 'admin' },
onLogout: () => { /* 退出登录逻辑 */ }
}
}
]);
子应用接收 props:
// 子应用的生命周期钩子中接收
export async function mount(props) {
console.log('收到主应用数据:', props.userInfo);
// 渲染子应用
render(props);
}
3.2.2 基于全局事件的通信(灵活但需谨慎)
有时候,子应用之间需要直接通信,或者主应用需要广播消息。这时候可以用 qiankun 提供的 initGlobalState。
嗯,这里要注意:全局状态是响应式的,但不要滥用。我曾经见过一个项目,全局状态里塞了几十个字段,最后谁也搞不清哪个字段是哪个子应用用的。
主应用初始化全局状态:
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({
theme: 'light',
language: 'zh-CN'
});
// 监听状态变化
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态变了:', state);
});
// 更新状态
actions.setGlobalState({ theme: 'dark' });
子应用监听和修改:
export async function mount(props) {
// 子应用通过 props 拿到 onGlobalStateChange 和 setGlobalState
props.onGlobalStateChange((state, prev) => {
console.log('子应用感知到状态变化:', state);
}, true); // 第二个参数 true 表示立即执行一次
// 子应用也可以修改全局状态
props.setGlobalState({ language: 'en' });
}
我的建议:能用 props 解决的,就别用全局状态。props 的传递链路清晰,出了问题容易排查。全局状态适合那些“多个子应用都需要实时感知”的场景,比如主题切换、语言切换。
3.3 生命周期钩子:子应用的“生老病死”
qiankun 的生命周期钩子,说白了就是子应用在什么时候该做什么事。每个子应用必须暴露三个钩子:bootstrap、mount、unmount。还有一个可选的 update。
| 钩子名称 | 触发时机 | 典型用途 |
|---|---|---|
| bootstrap | 子应用第一次加载时触发,只执行一次 | 初始化全局变量、加载公共资源 |
| mount | 每次子应用激活时触发 | 渲染 DOM、绑定事件、启动路由 |
| unmount | 每次子应用切走时触发 | 清理 DOM、解绑事件、销毁定时器 |
| update | 可选,当 props 变化时触发 | 响应式更新子应用内容 |
一个完整的子应用生命周期示例:
// 子应用入口文件
let appInstance = null;
export async function bootstrap() {
console.log('子应用初始化完成');
// 这里可以做一些一次性的初始化工作
// 比如加载一些公共的 CSS 或 JS
}
export async function mount(props) {
console.log('子应用挂载,接收到的 props:', props);
// 渲染子应用
appInstance = new Vue({
router,
render: h => h(App)
}).$mount('#app');
}
export async function unmount() {
console.log('子应用卸载');
// 销毁 Vue 实例
appInstance.$destroy();
appInstance = null;
// 清理 DOM
document.querySelector('#app').innerHTML = '';
}
export async function update(props) {
console.log('子应用更新,新 props:', props);
// 如果子应用需要响应 props 变化,在这里处理
}
我曾经踩过的坑:在 unmount 里忘记清理定时器或全局事件监听,导致子应用切走后,定时器还在跑,控制台报错。后来我养成了一个习惯——每次在 unmount 里都手动清理所有副作用,包括 setTimeout、addEventListener、以及 Vuex 的 store 状态。
3.4 实战中的避坑指南
光看理论不够,我结合自己的经验,给你列几个实际开发中容易踩的坑:
- 样式隔离:qiankun 默认用 Shadow DOM 做样式隔离,但有些 UI 库(比如 Ant Design)的弹窗会挂载到 body 上,导致样式丢失。我的做法是:在子应用 mount 时,手动把弹窗的容器指定到子应用自己的 DOM 节点下。
- 路由冲突:主应用和子应用的路由模式要一致。如果主应用用 history 模式,子应用也用 history 模式,否则跳转会出问题。
- 资源加载:子应用的静态资源(图片、字体)要用绝对路径或 CDN 地址,否则主应用加载时会 404。我习惯在子应用的 webpack 配置里设置
publicPath为//localhost:7101/这种形式。
一个小技巧:开发时,子应用可以独立运行(不依赖主应用)。你只需要在子应用的入口文件里判断一下:如果当前是独立运行,就自己挂载;如果是被 qiankun 加载,就暴露生命周期钩子。这样调试起来方便很多。
好了,这一章的内容就到这里。qiankun 的核心概念其实不复杂,但真正用好它,需要你对沙箱、通信、生命周期有深入的理解。下一章我们会聊 qiankun 的沙箱实现原理,到时候我会带你手写一个简易沙箱,敬请期待。