qiankun原理深度解析
qiankun 基于 single-spa 封装,补上了 HTML Entry(子应用零改造接入)与沙箱隔离(Proxy 拦截 window、样式作用域隔离)两块拼图。 理解 qiankun 本质是理解浏览器里如何实现应用级隔离,面试常考沙箱机制与 JS、CSS 隔离方案。
一句话概括
qiankun 是基于 single-spa 封装的微前端框架,它补上了 single-spa 缺失的两块拼图:HTML Entry(子应用几乎零改造接入)和沙箱隔离(Proxy 拦截 window 读写、样式作用域隔离)。理解 qiankun,本质是理解”浏览器里如何实现应用级隔离”这个底层问题。
核心知识点
1. HTML Entry:子应用零改造接入
single-spa 的 JS Entry 要求子应用自己打包成 UMD,接入成本高。qiankun 的 HTML Entry 直接 fetch 子应用的 HTML,解析出 JS/CSS 动态加载:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// HTML Entry 核心流程(简化)
async function loadEntry(appName, entryUrl) {
const html = await fetch(entryUrl).then(r => r.text());
const doc = new DOMParser().parseFromString(html, 'text/html');
const base = entryUrl.slice(0, entryUrl.lastIndexOf('/') + 1);
// 提取 script 的 src,相对路径转绝对
const scripts = [...doc.querySelectorAll('script')]
.map(s => s.src)
.filter(Boolean)
.map(src => src.startsWith('http') ? src : base + src);
// 提取样式
const styles = [...doc.querySelectorAll('link[rel="stylesheet"]')]
.map(l => l.href.startsWith('http') ? l.href : base + l.href);
return { template: doc.body.innerHTML, scripts, styles };
}
// 关键价值:子应用保持原有构建产物,无需为微前端单独打包
2. Proxy 沙箱:拦截 window 读写
沙箱的核心是”让子应用以为自己在操作 window,实际写进隔离对象”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Proxy 沙箱:active 时激活,inactive 时还原
class ProxySandbox {
active() {
this.fakeWindow = {};
this.proxy = new Proxy(window, {
get: (target, key) =>
key in this.fakeWindow ? this.fakeWindow[key] : target[key],
set: (target, key, value) => {
this.fakeWindow[key] = value; // 写进隔离对象,不污染真实 window
return true;
},
});
}
inactive() {
this.fakeWindow = {}; // 退出后销毁,全局零残留
}
}
// 不支持 Proxy 的旧浏览器回退到快照沙箱(进入存快照、退出还原)
3. 样式隔离:作用域前缀
子应用样式互相污染是高频坑。qiankun 提供两种隔离,核心都是”给选择器加前缀”:
1
2
3
4
// experimentalStyleIsolation:给子应用样式规则加前缀
// 原始:.title { color: red }
// 转换后:div[data-qiankun="app1"] .title { color: red }
// strictStyleIsolation:用 Shadow DOM 天然隔离(但要注意弹窗等默认挂 body 的问题)
4. 生命周期与通信
子应用遵循 bootstrap/mount/unmount 契约,通信用 initGlobalState 的发布订阅:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { initGlobalState } from 'qiankun';
// 基座侧:初始化全局状态
const actions = initGlobalState({ user: null });
actions.onGlobalStateChange((state, prev) => {
console.log('用户信息变化:', state.user);
});
actions.setGlobalState({ user: { name: '张三' } });
// 子应用侧:接收/更新全局状态
export async function mount(props) {
props.onGlobalStateChange((state) => console.log('子应用收到:', state.user));
props.setGlobalState({ cart: 3 }); // 子应用也能反向更新
}
其实你每天都在用
- 支付宝里点击不同 Tab 切换的业务模块:每个 Tab 可能就是一个 qiankun 子应用,切换时触发
mount/unmount,各自加载卸载。 - 中后台系统里”打开一个新菜单,页面秒开”:子应用资源被 qiankun 缓存,二次进入走缓存,无需重新 fetch HTML。
- 同一页面里两个风格完全不同的模块:一个 React 写的、一个 Vue 写的,样式互不干扰,背后就是 Proxy 沙箱 + 样式前缀在起作用。
- 老系统渐进式升级:公司那个用了 8 年的 AngularJS 后台,新页面用 qiankun 挂 React 子应用一点点替换,不用整体重写。
- 登录态在多个子应用间自动同步:你登录一次,购物车、订单、会员中心各个子应用都拿到用户信息,靠的就是
initGlobalState的全局状态广播。
常见误解(FAQ)
❌ 误区一:qiankun 和 single-spa 是竞争关系。 错误。qiankun 本身就是基于 single-spa 二次封装——single-spa 负责路由调度,qiankun 在其上加了 HTML Entry 和沙箱隔离。说”qiankun 比 single-spa 强”不准确,应该说”qiankun 是 single-spa 的开箱即用增强版”。
❌ 误区二:qiankun 的沙箱能隔离一切。 错误。Proxy 沙箱隔离的是 JS 全局变量,但隔离不了三类东西:直接操作 document 的代码、全局 CSS 选择器(需额外样式隔离)、以及 localStorage / cookie 这类真实共享存储。沙箱不是万能保险箱。
❌ 误区三:qiankun 子应用完全零改造。 错误。”零改造”是针对构建产物而言(不用重新打包 UMD),但业务代码通常要改:子应用要导出生命周期函数、要处理 publicPath、要避免污染全局、弹窗/全局样式要特殊处理。零改造是理想,小改造是现实。
❌ 误区四:用 Shadow DOM 做样式隔离就一劳永逸了。 错误。Shadow DOM 隔离彻底,但副作用明显:弹窗、下拉菜单默认挂载到 document.body,会跑到 Shadow Root 外导致样式失效;第三方组件库(如 antd 的 Modal)也常踩这个坑。所以 qiankun 默认用作用域前缀方案而非 Shadow DOM。
一句话总结
qiankun 的价值在于把微前端从”要自己造轮子”降到了”配置即用”——HTML Entry 降低接入成本、Proxy 沙箱解决变量污染、样式前缀解决样式冲突,而它的本质,是浏览器环境里应用级隔离的工程化答案。