微前端通信方案深度解析
一句话概括
微前端通信是跨子应用数据交换的设计模式集合,从最松耦合的 URL 参数传递、到中等耦合的自定义事件、再到紧耦合的全局共享状态和基座-子应用双向通信,每一种方案都有其适用场景与代价。一个微前端架构中通常同时存在 3-4 种通信方式,关键在于根据”数据是短暂的还是持久的”、”传递是单向的还是双向的”、”跨应用耦合度多高才可接受”三个维度做权衡。本文从通信模式分类、并发安全、状态同步、性能优化和源码实现五个维度展开。
背景与意义
微前端架构将一个单体应用拆分为多个独立子应用后,最直接的问题就是:”它们之间如何交换数据?” 场景包括但不限于:用户登录后购物车数量同步、商品详情页点击”加入购物车”后购物车 badge 更新、用户在 A 子应用中修改设置后 B 子应用实时反映等。通信方案的选择直接影响整个微前端架构的耦合度、维护成本和性能表现。面试中,微前端通信是架构设计的最高频追问点之一:”你们的微前端子应用之间怎么传递用户信息?”“如果子应用需要频繁通信,你怎么设计?”“全局状态和 URL 传参分别适合什么场景?”
概念与定义
紧耦合通信 (Tightly Coupled)
子应用之间直接引用对方的代码或状态。虽然效率最高但破坏了微前端的”独立部署”宗旨。
松耦合通信 (Loosely Coupled)
子应用之间通过约定的公共通道(如事件总线、URL 参数、基座中转)交换数据。子应用不需要知道对方的存在。
事件总线 (Event Bus)
发布/订阅模式的一种实现。任意子应用可以向总线发布事件,其他子应用可以订阅该事件。双方在代码层面零依赖。
全局状态 (Global State)
基座持有的共享状态对象。子应用通过基座提供的 API 读取和修改全局状态,修改后自动通知所有子应用。qiankun 的 initGlobalState 就是此模式。
URL 参数传递 (URL-based Communication)
通过 URL 的 query string 或 hash 片段传递数据。最松耦合的方式,但只能传递简单值和有限长度的数据。
核心知识点拆解
1. URL 参数通信方案
URL 参数是最简单、最可靠的微前端通信方式,不需要任何额外的库或框架:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// ===== URL 参数通信方案 =====
// 优点:松耦合、无框架依赖、刷新不丢失、SEO 友好(部分)
// 缺点:长度限制、只能传字符串、变更需要重新路由
// 1. Query String 方式
class URLQueryCommunicator {
constructor() {
this.params = new URLSearchParams(window.location.search);
this.listeners = new Map();
this.watchInterval = null;
}
// 设置参数(触发路由变化,但不重新加载页面)
setParam(key, value) {
const url = new URL(window.location.href);
url.searchParams.set(key, String(value));
// 使用 history.replaceState 更新 URL 但不触发页面刷新
window.history.replaceState(
{ ...window.history.state, ...this.getStateSnapshot() },
'',
url.toString()
);
// 通知监听者
this.notify(key, value);
}
// 批量设置参数
setParams(params) {
const url = new URL(window.location.href);
const changedKeys = [];
for (const [key, value] of Object.entries(params)) {
const strValue = String(value);
if (url.searchParams.get(key) !== strValue) {
url.searchParams.set(key, strValue);
changedKeys.push(key);
}
}
if (changedKeys.length > 0) {
window.history.replaceState(
{ ...window.history.state, ...this.getStateSnapshot() },
'',
url.toString()
);
// 批量通知
for (const key of changedKeys) {
this.notify(key, params[key]);
}
}
}
// 获取参数值
getParam(key, defaultValue = null) {
return this.params.get(key) || defaultValue;
}
// 获取所有参数
getAllParams() {
const result = {};
for (const [key, value] of this.params.entries()) {
result[key] = value;
}
return result;
}
// 订阅参数变化
onParamChange(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, new Set());
}
this.listeners.get(key).add(callback);
// 返回取消订阅函数
return () => {
this.listeners.get(key)?.delete(callback);
};
}
// 通知参数变化
notify(key, value) {
const callbacks = this.listeners.get(key);
if (callbacks) {
callbacks.forEach((cb) => {
try {
cb(value);
} catch (e) {
console.error(`[URL通信] 回调异常 (${key}):`, e);
}
});
}
}
// 获取当前所有参数的快照(用于 history.state)
getStateSnapshot() {
const snapshot = {};
for (const [key, value] of this.params.entries()) {
snapshot[key] = value;
}
return snapshot;
}
// 监听 popstate(浏览器前进/后退时的参数恢复)
startWatching() {
window.addEventListener('popstate', () => {
const oldParams = new Map(this.params);
this.params = new URLSearchParams(window.location.search);
// 检测变化的参数并通知
for (const [key, value] of this.params.entries()) {
if (oldParams.get(key) !== value) {
this.notify(key, value);
}
}
});
}
}
// 2. History State 方式(隐藏参数,不暴露在 URL 中)
class HistoryStateCommunicator {
constructor() {
this.state = window.history.state || {};
}
setState(key, value) {
this.state[key] = value;
window.history.replaceState(this.state, '');
}
getState(key, defaultValue = null) {
return this.state?.[key] ?? defaultValue;
}
// 监听 state 变化
onStateChange(callback) {
const handler = () => {
this.state = window.history.state || {};
callback(this.state);
};
window.addEventListener('popstate', handler);
return () => window.removeEventListener('popstate', handler);
}
}
// 使用示例
const urlComm = new URLQueryCommunicator();
// 子应用 A(商品详情)设置参数
function addToCart(productId) {
urlComm.setParam('cart_item', productId);
urlComm.setParam('cart_action', 'add');
}
// 子应用 B(购物车)监听参数变化
const unsubscribe = urlComm.onParamChange('cart_item', (productId) => {
fetch(`/api/cart/add/${productId}`).then(() => {
// 更新购物车 UI
refreshCartBadge();
});
});
// 清理
// unsubscribe();
2. 事件总线通信方案
事件总线是微前端中最灵活、使用最广泛的通信方式。它不依赖 URL 变化,可以传递复杂对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// ===== 事件总线通信方案 =====
// 基于发布/订阅模式,所有子应用通过同一个事件通道通信
// 完整的事件总线实现
class MicroEventBus {
constructor(options = {}) {
this.channels = new Map(); // 事件通道 → 回调集合
this.onceChannels = new Map(); // 一次性事件
this.history = new Map(); // 事件历史(用于后期订阅者获取)
this.maxHistoryPerEvent = options.maxHistoryPerEvent || 0; // 0 为不保存
this.enableDebug = options.debug || false;
this.eventId = 0;
// 如果提供了 window,则挂载到 window 上(跨子应用共享)
if (options.global) {
this.globalChannel = new Map();
// 使用 MutationObserver 或自定义事件跨应用通信
this.setupCustomEventBridge();
}
}
// === 核心 API ===
// 发布事件
emit(eventName, payload) {
this.eventId++;
const event = {
id: this.eventId,
name: eventName,
payload,
timestamp: Date.now(),
};
this.log(`📢 [EMIT] ${eventName}`, payload);
// 通知普通订阅者
const callbacks = this.channels.get(eventName);
if (callbacks) {
callbacks.forEach((callback) => {
this.safeInvoke(callback, event);
});
}
// 通知一次性订阅者
const onceCallbacks = this.onceChannels.get(eventName);
if (onceCallbacks) {
onceCallbacks.forEach((callback) => {
this.safeInvoke(callback, event);
});
this.onceChannels.delete(eventName); // 一次性事件自动清理回调
}
// 保存历史
if (this.maxHistoryPerEvent > 0) {
if (!this.history.has(eventName)) {
this.history.set(eventName, []);
}
const history = this.history.get(eventName);
history.push(event);
// 截断超出的历史
if (history.length > this.maxHistoryPerEvent) {
history.splice(0, history.length - this.maxHistoryPerEvent);
}
}
// 跨应用通知(如果是全局模式)
this.dispatchCustomEvent(event);
return event.id;
}
// 订阅事件
on(eventName, callback) {
if (!this.channels.has(eventName)) {
this.channels.set(eventName, new Set());
}
this.channels.get(eventName).add(callback);
this.log(`👂 [ON] ${eventName}`);
// 如果这个事件已经有历史数据,立即回放给新订阅者
if (this.maxHistoryPerEvent > 0 && this.history.has(eventName)) {
const history = this.history.get(eventName);
history.forEach((event) => {
this.safeInvoke(callback, event);
});
}
// 返回取消订阅函数
return () => {
this.off(eventName, callback);
};
}
// 一次性订阅
once(eventName, callback) {
if (!this.onceChannels.has(eventName)) {
this.onceChannels.set(eventName, new Set());
}
this.onceChannels.get(eventName).add(callback);
this.log(`🕐 [ONCE] ${eventName}`);
return () => {
this.onceChannels.get(eventName)?.delete(callback);
};
}
// 取消订阅
off(eventName, callback) {
const callbacks = this.channels.get(eventName);
if (callbacks) {
callbacks.delete(callback);
if (callbacks.size === 0) {
this.channels.delete(eventName);
}
}
this.log(`👋 [OFF] ${eventName}`);
}
// 等待事件(Promise 化)
waitFor(eventName, timeout = 10000) {
return new Promise((resolve, reject) => {
// 如果历史中有,立即返回
if (this.history.has(eventName) && this.history.get(eventName).length > 0) {
const history = this.history.get(eventName);
resolve(history[history.length - 1]);
return;
}
const timer = setTimeout(() => {
this.off(eventName, handler);
reject(new Error(`等待事件 ${eventName} 超时`));
}, timeout);
const handler = (event) => {
clearTimeout(timer);
resolve(event);
};
this.once(eventName, handler);
});
}
// === 跨界应用通信(通过 window 的 CustomEvent)===
setupCustomEventBridge() {
// 监听来自其他 window 的微前端事件
window.addEventListener('micro-app-event', (event) => {
const { appName, eventName, payload } = event.detail;
// 忽略自己发出的事件
if (appName === this.appName) return;
this.log(`📨 [CROSS] 来自 ${appName} 的事件: ${eventName}`);
// 转发到内部订阅者
const callbacks = this.channels.get(eventName);
if (callbacks) {
callbacks.forEach((cb) => {
this.safeInvoke(cb, { name: eventName, payload, from: appName });
});
}
});
}
dispatchCustomEvent(event) {
window.dispatchEvent(
new CustomEvent('micro-app-event', {
detail: {
appName: this.appName,
eventName: event.name,
payload: event.payload,
timestamp: event.timestamp,
},
})
);
}
// === 工具方法 ===
safeInvoke(callback, event) {
try {
callback(event);
} catch (e) {
console.error(`[EventBus] 事件处理器异常 (${event.name}):`, e);
}
}
// 获取事件统计
getStats() {
return {
channels: this.channels.size,
callbacks: Array.from(this.channels.values())
.reduce((sum, s) => sum + s.size, 0),
historyEvents: Array.from(this.history.values())
.reduce((sum, arr) => sum + arr.length, 0),
};
}
// 清理所有订阅
clear() {
this.channels.clear();
this.onceChannels.clear();
this.log('🧹 [CLEAR] 所有订阅已清理');
}
log(...args) {
if (this.enableDebug) {
console.log(`[EventBus]`, ...args);
}
}
}
// === 创建全局单例事件总线 ===
const globalEventBus = new MicroEventBus({
debug: true,
maxHistoryPerEvent: 5,
global: true,
});
// === 使用示例 ===
// 在子应用 A(商品详情)中:
function addToCart(product, quantity = 1) {
// 发布事件
globalEventBus.emit('cart:add', {
productId: product.id,
name: product.name,
price: product.price,
quantity,
addedAt: Date.now(),
});
}
// 在子应用 B(购物车 header)中:
const unsubscribe = globalEventBus.on('cart:add', (event) => {
const { productId, name, quantity } = event.payload;
// 更新购物车数量 Badge
updateCartBadge(incrementBy(quantity));
// 显示加入购物车的 Toast 通知
showToast(`已添加 ${name} 到购物车`);
});
// 等待特定事件
async function waitForPaymentResult() {
try {
const result = await globalEventBus.waitFor('payment:complete', 30000);
showSuccessPage(result.payload.orderId);
} catch (e) {
showPaymentTimeout();
}
}
3. 全局共享状态通信方案
全局共享状态是微前端中最”重”的通信方式,适合需要频繁同步的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// ===== 全局共享状态通信方案 =====
// 类似 Redux 的思想,但跨子应用共享
class GlobalStore {
constructor(initialState = {}) {
this.state = { ...initialState };
this.subscribers = new Map(); // 路径 → 回调集合
this.changeLog = [];
this.maxChangeLog = 50;
this.isBatchUpdating = false;
this.pendingUpdates = [];
}
// 获取状态
get(path) {
if (!path) return this.state;
return path.split('.').reduce((obj, key) => {
return obj?.[key];
}, this.state);
}
// 设置状态(触发通知)
set(path, value) {
if (this.isBatchUpdating) {
this.pendingUpdates.push({ path, value });
return;
}
const oldValue = this.get(path);
if (oldValue === value) return; // 无变化,不通知
// 更新状态
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => {
if (!(key in obj)) {
obj[key] = {};
}
return obj[key];
}, this.state);
target[lastKey] = value;
// 记录变更日志
this.logChange(path, oldValue, value);
// 通知订阅者
this.notify(path, value, oldValue);
// 也通知父路径的订阅者
keys.reduce((acc, key) => {
const ancestorPath = acc ? `${acc}.${key}` : key;
this.notify(ancestorPath, this.get(ancestorPath));
return ancestorPath;
}, '');
}
// 批量更新
batchUpdate(updater) {
this.isBatchUpdating = true;
try {
updater(this);
} finally {
this.isBatchUpdating = false;
// 处理积累的更新
const updates = [...this.pendingUpdates];
this.pendingUpdates = [];
// 批量完成后,逐条通知
// 注意:这里不合并通知,因为每个路径独立
updates.forEach(({ path, value }) => {
this.set(path, value);
});
}
}
// 订阅状态变化
subscribe(path, callback) {
if (!this.subscribers.has(path)) {
this.subscribers.set(path, new Set());
}
this.subscribers.get(path).add(callback);
// 立即回调当前值
callback(this.get(path));
// 返回取消订阅函数
return () => {
this.subscribers.get(path)?.delete(callback);
};
}
// 通知订阅者
notify(path, newValue, oldValue) {
const callbacks = this.subscribers.get(path);
if (callbacks) {
callbacks.forEach((cb) => {
try {
cb(newValue, oldValue);
} catch (e) {
console.error(`[GlobalStore] 订阅回调异常 (${path}):`, e);
}
});
}
// 通知全匹配订阅('*' 订阅所有变更)
const allCallbacks = this.subscribers.get('*');
if (allCallbacks) {
allCallbacks.forEach((cb) => {
try {
cb({ path, newValue, oldValue });
} catch (e) {
console.error('[GlobalStore] 全匹配订阅异常:', e);
}
});
}
}
// 重置状态
reset(newState = {}) {
const oldState = { ...this.state };
this.state = { ...newState };
// 通知所有顶层路径变化
Object.keys(newState).forEach((key) => {
this.notify(key, newState[key], oldState[key]);
});
}
// 持久化到 localStorage
persist(keys = null) {
const data = keys
? keys.reduce((obj, key) => ({ ...obj, [key]: this.state[key] }), {})
: this.state;
try {
localStorage.setItem('micro_app_state', JSON.stringify(data));
} catch (e) {
console.warn('[GlobalStore] 持久化失败:', e);
}
}
// 从 localStorage 恢复
restore() {
try {
const saved = localStorage.getItem('micro_app_state');
if (saved) {
const data = JSON.parse(saved);
this.state = { ...this.state, ...data };
}
} catch (e) {
console.warn('[GlobalStore] 恢复失败:', e);
}
}
logChange(path, oldValue, newValue) {
this.changeLog.push({
path,
oldValue,
newValue,
timestamp: Date.now(),
});
if (this.changeLog.length > this.maxChangeLog) {
this.changeLog.shift();
}
}
// 获取变更历史
getChangeLog() {
return [...this.changeLog];
}
}
// === 使用示例 ===
// 基座应用中创建全局 Store
const store = new GlobalStore({
user: { id: null, name: '', role: 'guest' },
cart: { count: 0, items: [] },
config: { theme: 'light', language: 'zh-CN' },
});
// 子应用 A(登录模块)中:
store.set('user', { id: 'u123', name: '张三', role: 'vip' });
// 子应用 B(购物车 Badge)中:
const unsubscribe = store.subscribe('cart.count', (count) => {
document.querySelector('#cart-badge').textContent = count > 0 ? count : '';
});
// 批量更新
store.batchUpdate((s) => {
s.set('cart.items', newItems);
s.set('cart.count', newItems.length);
s.set('cart.totalPrice', calculateTotal(newItems));
});
4. 基座-子应用双向通信
通过基座作为中介的通信方案。子应用不直接通信,都通过基座中转:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// ===== 基座-子应用双向通信方案 =====
// 基座作为通信中介,子应用之间不直接通信
// 基座端实现
class MicroAppCommBridge {
constructor() {
this.apps = new Map(); // 已注册的子应用
this.proxies = new Map(); // 子应用通信代理
this.messageQueue = []; // 排队消息
}
// 注册子应用
registerApp(appName, lifecycleHooks) {
// 为子应用创建一个通信代理
const proxy = this.createAppProxy(appName);
this.apps.set(appName, {
name: appName,
hooks: lifecycleHooks,
proxy,
status: 'registered',
});
return proxy;
}
// 创建子应用通信代理
createAppProxy(appName) {
const bridge = this;
return {
// 向其他子应用发送消息
sendTo(targetApp, event, payload) {
bridge.routeMessage(appName, targetApp, event, payload);
},
// 广播给所有子应用
broadcast(event, payload) {
bridge.broadcastMessage(appName, event, payload);
},
// 监听来自基座或其他子应用的消息
onMessage(handler) {
bridge.addMessageHandler(appName, handler);
},
// 请求基座的能力
request(capability, params) {
return bridge.handleCapability(appName, capability, params);
},
};
}
// 路由消息到目标子应用
routeMessage(fromApp, toApp, event, payload) {
const target = this.apps.get(toApp);
if (!target || target.status !== 'mounted') {
// 目标未就绪,排队
this.messageQueue.push({
type: 'direct',
from: fromApp,
to: toApp,
event,
payload,
timestamp: Date.now(),
});
console.warn(`[CommBridge] 子应用 "${toApp}" 未就绪,消息已排队`);
return;
}
this.deliver(fromApp, toApp, event, payload);
}
// 广播消息
broadcastMessage(fromApp, event, payload) {
for (const [name, app] of this.apps) {
if (name !== fromApp && app.status === 'mounted') {
this.deliver(fromApp, name, event, payload);
}
}
}
// 投递消息
deliver(from, to, event, payload) {
const handlers = this.messageHandlers?.get(to);
if (handlers) {
handlers.forEach((handler) => {
try {
handler({ from, event, payload, timestamp: Date.now() });
} catch (e) {
console.error(`[CommBridge] 消息投递异常 (${from}→${to}):`, e);
}
});
}
}
// 添加消息处理器
addMessageHandler(appName, handler) {
if (!this.messageHandlers) {
this.messageHandlers = new Map();
}
if (!this.messageHandlers.has(appName)) {
this.messageHandlers.set(appName, new Set());
}
this.messageHandlers.get(appName).add(handler);
}
// 处理子应用对基座能力的请求
async handleCapability(appName, capability, params) {
switch (capability) {
case 'fetch':
// 基座代理 API 请求(可添加统一鉴权)
return this.proxyFetch(params);
case 'navigate':
// 基座控制路由跳转
this.proxyNavigate(params);
return true;
case 'getSharedData':
// 获取全局共享数据
return this.getSharedData(params);
default:
throw new Error(`未知能力: ${capability}`);
}
}
proxyFetch({ url, options }) {
// 添加统一的鉴权 header
const headers = {
...options?.headers,
'X-Requested-By': 'micro-app-bridge',
};
return fetch(url, { ...options, headers });
}
proxyNavigate({ to, replace = false }) {
if (replace) {
window.history.replaceState({ from: 'micro-app' }, '', to);
} else {
window.history.pushState({ from: 'micro-app' }, '', to);
}
// 触发路由变化
window.dispatchEvent(new PopStateEvent('popstate'));
}
getSharedData(key) {
// 从基座维护的共享数据源获取
return this.sharedData?.[key];
}
// 子应用挂载完成后的回调
onAppMounted(appName) {
const app = this.apps.get(appName);
if (app) {
app.status = 'mounted';
// 投递排队的消息
this.drainQueue(appName);
}
}
drainQueue(appName) {
const pending = this.messageQueue.filter(m => m.to === appName);
this.messageQueue = this.messageQueue.filter(m => m.to !== appName);
pending.forEach((msg) => {
this.deliver(msg.from, appName, msg.event, msg.payload);
});
}
// 子应用卸载
onAppUnmounted(appName) {
const app = this.apps.get(appName);
if (app) {
app.status = 'unmounted';
}
}
}
// === 使用示例 ===
// 基座创建通信桥梁
const bridge = new MicroAppCommBridge();
// 商品子应用注册
const productProxy = bridge.registerApp('products', {
onMount() {},
});
// 购物车子应用注册
const cartProxy = bridge.registerApp('cart', {
onMount() {},
});
// 商品子应用中:
function onBuyClick(product) {
// 通过基座向购物车发送消息
productProxy.sendTo('cart', 'addItem', {
productId: product.id,
name: product.name,
price: product.price,
quantity: 1,
});
}
// 购物车子应用中:
cartProxy.onMessage((msg) => {
if (msg.event === 'addItem') {
const { productId, name, price, quantity } = msg.payload;
updateCart(productId, quantity);
showNotification(`已添加 ${name}`);
}
});
// 商品子应用请求基座的 fetch 能力
async function getProductData(productId) {
const data = await productProxy.request('fetch', {
url: `/api/products/${productId}`,
options: { method: 'GET' },
});
return data.json();
}
5. 并发安全与状态一致性问题
当多个子应用同时修改共享状态时,必须防止竞态条件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// ===== 并发安全的共享状态管理 =====
// 使用简单的锁机制防止写冲突
class LockableGlobalStore {
constructor(initialState = {}) {
this.state = { ...initialState };
this.locks = new Map(); // 路径 → 锁信息
this.pendingWrites = new Map(); // 路径 → 等待队列
this.lockTimeout = 3000; // 锁超时时间
}
// 请求写锁
async acquireLock(path, holder) {
if (!this.locks.has(path)) {
this.locks.set(path, { holder, acquiredAt: Date.now() });
return true;
}
const existing = this.locks.get(path);
// 检查锁是否超时
if (Date.now() - existing.acquiredAt > this.lockTimeout) {
console.warn(`[LockableStore] 路径 "${path}" 的锁已超时,强制释放`);
this.locks.set(path, { holder, acquiredAt: Date.now() });
return true;
}
// 锁被占用,排队等待
return new Promise((resolve) => {
if (!this.pendingWrites.has(path)) {
this.pendingWrites.set(path, []);
}
this.pendingWrites.get(path).push({ holder, resolve });
});
}
// 释放写锁
releaseLock(path) {
this.locks.delete(path);
// 检查是否有等待的写入
const queue = this.pendingWrites.get(path);
if (queue && queue.length > 0) {
const next = queue.shift();
this.locks.set(path, { holder: next.holder, acquiredAt: Date.now() });
next.resolve(true);
if (queue.length === 0) {
this.pendingWrites.delete(path);
}
}
}
// 带锁的原子写入
async atomicSet(path, value, holder) {
const locked = await this.acquireLock(path, holder);
if (!locked) return false;
try {
const keys = path.split('.');
const lastKey = keys.pop();
const target = keys.reduce((obj, key) => obj[key], this.state);
target[lastKey] = value;
return true;
} finally {
this.releaseLock(path);
}
}
// 带锁的原子更新(基于当前值计算新值)
async atomicUpdate(path, updater, holder) {
const locked = await this.acquireLock(path, holder);
if (!locked) return null;
try {
const oldValue = this.get(path);
const newValue = updater(oldValue);
this.set(path, newValue);
return newValue;
} finally {
this.releaseLock(path);
}
}
}
// ===== 版本戳冲突检测(另一种方案)=====
// 每个修改携带版本号,检测到冲突时通知调用方
class VersionedState {
constructor() {
this.state = {};
this.versions = {};
}
set(path, value, expectedVersion) {
const currentVersion = this.versions[path] || 0;
// 如果调用方传入期望的版本号,做冲突检测
if (expectedVersion !== undefined && currentVersion !== expectedVersion) {
throw new ConflictError(
`版本冲突: 路径 "${path}" 当前版本 ${currentVersion},期望 ${expectedVersion}`
);
}
this.state[path] = value;
this.versions[path] = currentVersion + 1;
return this.versions[path]; // 返回新版本号
}
}
class ConflictError extends Error {
constructor(message) {
super(message);
this.name = 'ConflictError';
}
}
实战案例:完整的多方案集成
以下是一个整合了 URL 参数、事件总线、全局状态和基座通信的全栈通信方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// ============ 微前端通信集成 SDK ============
class MicroFrontendCommKit {
constructor(options = {}) {
this.appName = options.appName || 'unknown';
this.mode = options.mode || 'qiankun'; // qiankun / custom / module-federation
// 1. URL 通信(最松耦合)
this.urlComm = new URLQueryCommunicator();
// 2. 事件总线(中等耦合)
this.eventBus = new MicroEventBus({
debug: options.debug,
maxHistoryPerEvent: 3,
});
this.eventBus.appName = this.appName;
// 3. 全局状态(较紧耦合)
this.globalStore = new GlobalStore(options.initialState || {});
// 4. 基座通信(如果运行在微前端环境中)
if (window.__POWERED_BY_QIANKUN__) {
this.setupQiankunBridge(options.qiankunProps);
}
}
// qiankun 桥接
setupQiankunBridge(props) {
if (!props) return;
const { onGlobalStateChange, setGlobalState } = props;
// 将 qiankun 的全局状态同步到我们的 Store
if (onGlobalStateChange) {
onGlobalStateChange((state, prev) => {
Object.entries(state).forEach(([key, value]) => {
this.globalStore.set(key, value);
});
}, true);
}
// 当 Store 变化时同步回 qiankun
const originalSet = this.globalStore.set;
this.globalStore.set = (path, value) => {
originalSet.call(this.globalStore, path, value);
if (setGlobalState) {
setGlobalState({ [path]: value });
}
};
this.qiankunProps = props;
this.qiankunConnected = true;
}
// ===== 统一的通信 API =====
// 发送消息(根据数据特点自动选择合适方案)
send(message) {
const { type, target, event, payload, urgent } = message;
if (type === 'navigate' || type === 'filter' || type === 'page') {
// 适合 URL 传递:简单、需要持久化、刷新后保留
this.urlComm.setParam(event, JSON.stringify(payload));
this.log(`📨 [URL] ${event}`, payload);
} else if (urgent || target) {
// 需要即时、定向传递:使用事件总线
if (target) {
this.eventBus.emit(`${target}:${event}`, payload);
} else {
this.eventBus.emit(event, payload);
}
this.log(`📨 [EventBus] ${event}`, payload);
} else {
// 频繁更新的全局数据:使用 Store
this.globalStore.set(event, payload);
this.log(`📨 [Store] ${event}`, payload);
}
}
// 接收消息
receive(channel, callback) {
// URL 参数
if (channel === 'url') {
return this.urlComm.onParamChange('*', callback);
}
// 事件总线
if (channel.startsWith('event:')) {
const eventName = channel.slice(6);
return this.eventBus.on(eventName, callback);
}
// 全局 Store
if (channel.startsWith('store:')) {
const path = channel.slice(6);
return this.globalStore.subscribe(path, callback);
}
// 基座消息
if (channel === 'bridge' && this.qiankunConnected) {
return this.qiankunProps.onGlobalStateChange(callback, true);
}
throw new Error(`未知通信通道: ${channel}`);
}
log(...args) {
if (this.constructor.debug) {
console.log(`[CommKit:${this.appName}]`, ...args);
}
}
}
// === 使用示例 ===
// 子应用 A(商品详情)
const commA = new MicroFrontendCommKit({
appName: 'products',
mode: 'qiankun',
debug: true,
qiankunProps: window.__POWERED_BY_QIANKUN__ ? {
onGlobalStateChange: window.__qiankun_onGlobalStateChange__,
setGlobalState: window.__qiankun_setGlobalState__,
} : null,
});
// 加入购物车时
function addToCart() {
// 1. 全局状态更新(同步购物车数量)
commA.send({
type: 'state',
event: 'cart.count',
payload: currentCartCount + 1,
});
// 2. 事件通知(显示 Toast)
commA.send({
type: 'event',
event: 'cart:added',
payload: { productId: 'p123', name: 'iPhone 16' },
urgent: true,
});
// 3. URL 更新(保持状态的 URL 可分享)
commA.send({
type: 'navigate',
event: 'selected_product',
payload: { from: 'products', id: 'p123' },
});
}
// 子应用 B(购物车 Badge)
const commB = new MicroFrontendCommKit({
appName: 'cart',
mode: 'qiankun',
debug: true,
});
// 订阅购物车数量变化
commB.receive('store:cart.count', (count) => {
document.getElementById('badge').textContent = count > 0 ? count : '';
});
// 订阅加入购物车事件(显示 Toast)
commB.receive('event:cart:added', (event) => {
showToast(`已添加: ${event.payload.name}`);
});
// 监听 URL 参数变化
commB.receive('url', (param, value) => {
if (param === 'selected_product') {
highlightProduct(JSON.parse(value).id);
}
});
底层原理
CustomEvent 的跨应用通信本质
通过 window.dispatchEvent(new CustomEvent(...)) 在同一个浏览器上下文中,所有 JavaScript 执行环境(包括 iframe 以外的所有主文档代码)都能收到同一个事件。这是因为 CustomEvent 在 DOM 事件模型中属于冒泡事件——即使由某个子应用触发,事件也会冒泡到 document 和 window,从而被所有子应用监听到。
但是需要注意:Micro Frontends 中的子应用共享同一个 window 对象(这是微前端与 iframe 的核心区别),因此通过 CustomEvent 的通信只适用于基于路由组合的微前端方案(qiankun、single-spa、Module Federation),不适用于基于 iframe 的方案。
通信的性能开销
不同通信方案的单次消息开销(以 Chrome 85+ 为基准):
- URL 参数:约 0.3ms(主要来自
history.replaceState调用和浏览器 URL 解析) - CustomEvent:约 0.05ms(微任务级别,几乎无开销)
- 全局 Store + 通知:约 0.1-0.5ms(取决于订阅者数量)
- postMessage(iframe 间):约 0.5-1ms(序列化 + 跨上下文)
高频面试题解析
面试题 1:微前端子应用之间通信,怎样做最松耦合?怎样最紧耦合?各自的优缺点是什么?
答案要点: 最松耦合:URL 参数传递。子应用完全不感知对方的存在,只是通过 URL 的 query/hash 交换信息。优点:完全不耦合,刷新不丢失,URL 可分享。缺点:只能传字符串(需序列化),长度受限,不适合高频更新。最紧耦合:直接 import 对方代码或共享同一个 Store 对象。优点:类型安全,无序列化开销,响应快。缺点:子应用间存在依赖,破坏独立部署原则,一方的变更可能导致另一方异常。推荐策略:首选 URL + 事件总线,尽量避免直接引用对方代码。
面试题 2:qiankun 的 onGlobalStateChange 是怎么实现的?如果多个子应用同时 setGlobalState 会怎样?
答案要点: qiankun 内部维护一个全局状态对象和一个监听器列表。setGlobalState 调用时,qiankun 遍历所有子应用的 onGlobalStateChange 回调并依次执行。如果多个子应用同时 setGlobalState,由于 JavaScript 是单线程的不会有真正的并发问题——回调按照事件循环的顺序串行执行。但可能存在”逻辑并发”:子应用 A 基于旧值计算新值,子应用 B 也基于旧值计算,后执行的会覆盖前者导致 A 的更新丢失。解决方案:在子应用中使用”函数式更新”(类似 React 的 setState(prev => new))或版本戳机制。
面试题 3:微前端中用户登录状态如何保持同步?
答案要点: 推荐方案:1)Token 存 Cookie(httpOnly + SameSite=Strict)——所有子应用自动携带 Cookie,无需显式传递;2)基座持有用户信息,通过 setGlobalState({ user: {...} }) 在子应用挂载时传递;3)每个子应用挂载后通过 API 获取用户信息(但 N 个子应用产生 N 个请求,浪费)。更优方案:基座将用户信息作为子应用挂载时的 props 传递 + Cookie 兜底。子应用只读取 props 中的 user 信息,不自行获取。
面试题 4:事件总线在微前端中有什么坑?如何防止内存泄漏?
答案要点: 坑:1)子应用卸载时忘记取消订阅,导致”幽灵回调”持续执行;2)事件名冲突——两个子应用用了相同的 event name;3)回调中抛出异常会阻塞其他回调的执行。解决方案:1)子应用卸载时必须调用取消订阅函数(qiankun 的 unmount 生命周期中清理);2)事件名使用命名空间(如 products:cartAdded、checkout:cartAdded);3)每个回调用 try-catch 包裹;4)建立订阅者生命周期管理——在子应用 unmount 时自动清除该应用的所有订阅。
面试题 5:如何在微前端架构中实现跨子应用的”暗黑模式”主题切换?
答案要点: 方案设计:1)基座维护全局 theme 状态并通过 onGlobalStateChange 同步给所有子应用;2)方案 A(CSS 变量):基座修改 document.documentElement.style.setProperty('--bg-color', darkBg),所有子应用使用 CSS 变量引用,无需通信。这是最推荐的方式。3)方案 B(类名切换):基座在 body 上切换 class(dark/light),子应用的样式使用 .dark .my-component {} 覆盖。4)方案 C(事件广播):全局 Store 的 theme 变化时,通过事件总线通知所有子应用,子应用各自处理样式切换。
总结与扩展
知识体系图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
微前端通信方案
├── 按照耦合度排序
│ ├── 🌱 URL 参数(最松耦合)
│ │ ├── Query String(页面可见)
│ │ ├── Hash(前端路由兼容)
│ │ └── History State(隐藏参数)
│ ├── 🌿 事件总线(中等耦合)
│ │ ├── 发布/订阅模式
│ │ ├── CustomEvent 跨应用
│ │ └── 一次性事件(once)
│ ├── 🌳 全局共享状态(较紧耦合)
│ │ ├── 基座 Store + onGlobalStateChange
│ │ ├── 独立 EventEmitter
│ │ └── Redux/Vuex 跨应用
│ └── 🌲 基座中转(紧耦合)
│ ├── 基座代理 API 请求
│ ├── 基座控制路由跳转
│ └── 基座分发消息
├── 根据数据特征选择
│ ├── 短暂数据 → 事件总线
│ ├── 持久数据 → 全局 Store + 持久化
│ ├── 页面级共享 → URL 参数
│ └── 跨应用 API → 基座代理
├── 安全性
│ ├── 数据校验(防止 XSS)
│ ├── 事件命名冲突检测
│ ├── 子应用卸载自动清理
│ └── 版本戳冲突检测
└── 性能优化
├── 批量更新
├── 按路径订阅而非全量
├── 防抖动(高频事件合并)
└── 懒加载监听(用到再注册)
延伸阅读
- qiankun initGlobalState 源码: qiankun 源码中的
globalState.ts - 发布/订阅模式设计: JavaScript 设计模式中的 Observer 模式详解
- postMessage API: MDN 文档——iframe 间通信的标准方案
- CSS 变量主题切换: 使用 CSS Custom Properties 实现零通信主题切换
- 微前端状态共享最佳实践: Martin Fowler 文章中关于”共享状态”与”共享边界”的讨论