一、理解 qiankun 的沙箱隔离
如果你们团队已经决定把中后台系统拆成微前端,大概率会选 qiankun。它接入成本低,社区活跃,而且踩过坑的人足够多。但很多人把 qiankun 成功接入后,反而陷入了另一种麻烦:子应用跑起来了,可主应用偶尔被改掉样式;A 子应用和 B 子应用同时挂载时,脚本互相报错;至于跨应用通信,最后往往变成了没人敢动的全局对象。这些问题都指向同一个核心:微前端的沙箱隔离、样式隔离与通信机制,不能只靠框架兜底,必须有一套完整方案。

先说 JS 沙箱。微前端最大的隐患之一,就是多个应用共享同一个 JavaScript 运行时。子应用里的全局变量、事件监听、定时器,都可能污染主应用或其他子应用。qiankun 的沙箱本质上解决的是 window 对象的读写隔离。
Proxy 沙箱与快照沙箱
qiankun 里有两种核心实现。第一种是基于 Proxy 的沙箱,适用于支持 ES6 的现代浏览器:qiankun 创建一个 Proxy 对象,子应用执行时代码上下文被 with 指向这个代理对象,所有对 window 的操作都会被拦截;子应用卸载时,qiankun 会通过记录的 key 来还原被修改过的全局属性。第二种是基于快照的沙箱,用于不支持 Proxy 的老旧浏览器,在子应用激活前保存 window 快照,卸载时整体恢复差异,性能开销明显更大。
// 简化版 qiankun Proxy 沙箱思路
const sandboxPool = {};
const sandboxProxy = new Proxy(window, {
set(target, key, value) {
sandboxPool[key] = value;
target[key] = value;
return true;
},
get(target, key) {
if (sandboxPool[key] !== undefined) {
return sandboxPool[key];
}
return target[key];
}
});
这个简化代码能帮助你理解基本思路,真实沙箱还会处理很多边界情况,比如不可配置属性、eval 劫持、原生方法绑定等。核心行为仍是:你可以对全局对象进行修改,但你的修改会被记录,并在应用卸载时被还原。
需要明确的是,沙箱不是安全边界。它拦截的是子应用代码直接通过 window 的读写,但动态创建的 script、window.parent 这类访问仍然可以绕过沙箱。所以不要把所有安全性寄托在 qiankun 的沙箱上,子应用团队仍然需要有「不随意操作全局对象」的自觉。
二、样式隔离:从实验策略到 Shadow DOM
样式冲突比 JS 冲突更隐蔽。JS 冲突通常立刻报错,样式冲突却经常表现为按钮突然变大、弹窗背景变灰、页面字体被篡改,排查起来非常消耗时间。qiankun 提供两个核心配置来控制样式隔离。
experimentalStyleIsolation 的做法是给子应用容器的所有静态样式选择器加上 data-qiankun 前缀。例如 .btn 会被改写成 data-qiankun=’sub-app’ 前缀,这样子应用的 CSS 就被限制在自身作用域内。它的问题是对动态插入的 style 标签和内联样式无效,而且某些全局选择器会被改写后失效。
strictStyleIsolation 则更进一步,通过为子应用容器挂载一个 Shadow DOM 实现完全隔离。借用 Shadow DOM 的特性,样式、DOM 都独立于主文档。但代价也很明显:很多 UI 库会把弹窗、抽屉组件挂载到 document.body 上,脱离 Shadow DOM 后样式全部丢失,需要额外处理。
一个典型的 qiankun 配置如下:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'sub-app',
entry: '//localhost:7101',
container: '#subapp-viewport',
activeRule: '/sub',
},
], {
sandbox: {
strictStyleIsolation: false,
experimentalStyleIsolation: true,
},
});
start();
两种模式并不存在绝对的好坏,关键是匹配业务约束。下表总结了两者的区别:
| 隔离方式 | 实现原理 | 隔离程度 | 主要问题 | 适用场景 |
|---|---|---|---|---|
| 不开启配置 | 仅由 qiankun 挂载卸载样式 | 低 | 动态样式与全局样式需靠人工控制 | 内部系统、样式结构简单 |
| experimentalStyleIsolation | 选择器加 data-qiankun 属性前缀 | 中 | 动态样式无法隔离,全局选择器失效 | 大多数中后台子应用 |
| strictStyleIsolation | 创建 Shadow DOM 容器 | 高 | 挂载到 body 的组件样式失效 | 子应用独立弹窗少,且样式要求高 |
这里容易有一个误区:认为开启样式隔离后,CSS 规范就不再重要。实际上,qiankun 的样式隔离只是工具,运行时插入的 style、第三方库的内联样式,它未必能管得到。比较好的做法是「框架隔离 + 工程规范」双管齐下,给子应用加上明确的前缀或使用 CSS Modules。
三、通信机制:全局状态与事件总线
微前端里通信是高频需求,但也是被滥用最严重的地方。很多团队把所有共享数据一股脑塞进全局变量,最后变成无人敢维护的隐形依赖。通信不是越多越好,而是要在合适的层级使用合适的机制。
qiankun 官方提供的全局状态方案是 initGlobalState,它由主应用创建,作为 props 传给所有已注册的子应用。子应用在生命周期的 mount 钩子里可以拿到 onGlobalStateChange 与 setGlobalState,实现订阅和更新。
主应用侧:
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({
user: null,
theme: 'light',
});
actions.onGlobalStateChange((state, prev) => {
console.log('全局状态变化', state, prev);
});
子应用侧:
export async function mount(props) {
const { onGlobalStateChange, setGlobalState } = props;
onGlobalStateChange((state, prev) => {
// 根据最新的全局状态更新视图
});
setGlobalState({ fromSubApp: true });
}
initGlobalState 的优势是官方、直观,适合用户信息、主题、权限这类跨应用共享的基础状态。但它也有局限:全局状态是单层结构,变更通过 diff 触发,没有模块化和时序管理,状态一多就会难以追踪。
另一种常见方案是事件总线,可以使用 mitt、EventEmitter,或者直接用浏览器原生的 CustomEvent。事件总线比全局状态更灵活,适合下发一次性动作,比如「打开某个详情页」或「刷新列表」。不过事件名管理容易冲突,也没有类型约束,调试时经常需要全局搜事件名。
两种方式可以共存,但要有边界。我的建议是:基础数据用 GlobalState,临时动作用事件总线,跨子应用跳转优先走路由。
| 通信方式 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| qiankun GlobalState | 官方支持,数据结构清晰 | 状态多后难维护 | 用户、主题、权限等公共状态 |
| 事件总线(mitt/CustomEvent) | 解耦灵活,按需订阅 | 事件名管理乱,难排查 | 跨子应用的一次性动作 |
| 路由参数 | 语义化,可收藏可刷新 | 只能传递简单数据 | 页面跳转、详情 ID |
四、落地建议与常见坑
在这里总结一下比较务实的接入路径。先从最小范围开始:主应用只负责路由和应用挂载,接入一个非核心子应用,跑通加载卸载和隔离效果。然后再逐步增加子应用。
- 第一步:确认沙箱开启,重点验证全局变量、定时器、事件监听是否被清理。
- 第二步:开启 experimentalStyleIsolation,跑一遍自动化回归,重点看第三方 UI 组件的样式。
- 第三步:筛选真正需要跨应用共享的数据,设计通信协议,避免所有状态都进入全局。
推进过程中,下面这些坑几乎每个团队都会遇到至少一个:
- 子应用卸载时没有清理全局事件监听,导致重复触发或内存泄漏。
- strictStyleIsolation 下弹窗挂载到 body,样式错乱,需要通过容器或公共样式补丁解决。
- 第三方库读取真实 window 时,在沙箱内出现兼容问题,需要使用 qiankun 提供的 ignore 机制或 external 方案。
还有一个常被忽略的点:性能。qiankun 默认会 prefetch 所有未激活的子应用,如果系统部署在公网或移动端,建议关闭 prefetch,改为手动预加载。
写在最后
qiankun 不是一个开箱即用的终极方案,而是一组解决微前端基础问题的工具。沙箱隔离解决 JS 污染,样式隔离解决 CSS 冲突,通信机制解决跨应用协作。它们各有边界,也各有代价。真正让微前端稳定运行的,是团队在工程规范上达成的共识,而不是某一个框架的配置项。如果你的团队正在规划微前端,不妨先隔离,再通信,最后再根据业务演进逐步完善自己的约束体系。
原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/642/