在 微前端(MFE, Micro Frontends) 中使用 CustomEvent 是非常常见的,尤其是在不同子应用(Micro Apps)之间通信 的时候。今天将以React 主应用 ↔ Angular 子应用 之间的通信进行示例
React 主应用 <-> Angular 子应用 用 mitt(事件总线) 通信示例
1. React 主应用
-
通过 props 给 Angular 子应用传递数据和回调函数
-
使用 mitt(事件总线)进行异步事件广播
javascript
// React 主应用示例(App.jsx)
import React, { useState, useEffect } from 'react';
import mitt from 'mitt';
const eventBus = mitt();
window.eventBus = eventBus;
function loadAngularApp(props) {
// 假设 Angular 应用挂载到 #angular-container
// 并且 Angular 子应用暴露了全局启动函数 angularApp.mount
window.angularApp.mount(document.getElementById('angular-container'), props);
}
export default function App() {
const [message, setMessage] = useState('');
useEffect(() => {
// 监听来自子应用的消息
eventBus.on('from-angular', (msg) => setMessage(msg));
return () => eventBus.off('from-angular');
}, []);
const onNotifyFromReact = (msg) => {
setMessage('React 接收到子应用消息:' + msg);
};
const propsForAngular = {
user: { id: 1, name: 'ReactUser' },
notifyParent: onNotifyFromReact,
};
useEffect(() => {
loadAngularApp(propsForAngular);
}, []);
return (
<div>
<h1>React 主应用</h1>
<p>消息:{message}</p>
<div id="angular-container" />
<button onClick={() => eventBus.emit('from-react', 'React 主动发消息')}>
React 向 Angular 发送消息
</button>
</div>
);
}
2. Angular 子应用
-
暴露
mount
方法接收父应用传来的 props -
通过传入的回调
notifyParent
通知 React -
使用
window.eventBus
监听 React 发来的事件
javascript
// Angular 子应用核心代码(app.component.ts + bootstrap)
import { Component, Input, OnDestroy } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h2>Angular 子应用</h2>
<p>接收的用户:{{ user?.name }}</p>
<button (click)="notifyReact()">通知 React</button>
`,
})
export class AppComponent implements OnDestroy {
@Input() user: any;
@Input() notifyParent!: (msg: string) => void;
private onFromReact = (msg: string) => alert('Angular 收到 React 消息: ' + msg);
ngOnInit() {
window.eventBus.on('from-react', this.onFromReact);
}
notifyReact() {
if (this.notifyParent) this.notifyParent('来自 Angular 的消息');
}
ngOnDestroy() {
window.eventBus.off('from-react', this.onFromReact);
}
}
// 暴露给主应用挂载用的方法
export function mount(container: HTMLElement, props: any) {
const moduleRef = platformBrowserDynamic().bootstrapModule(AppModule);
// 这里需要将 props 注入到 Angular 应用,比如用 InjectionToken 或服务
// 简单示意:
const appRef = moduleRef.injector.get(ApplicationRef);
// 把 props 传给 AppComponent,具体实现因项目不同略有差异
// 也可以通过全局变量或服务传递
container.appendChild(document.createElement('app-root'));
// 实际渲染交给 Angular
}
3. 关键点总结
方式 | 说明 |
---|---|
props | React 主应用传给 Angular 子应用用户数据和回调函数 |
回调函数 | Angular 调用回调函数通知 React 主应用 |
mitt 事件总线 | React 和 Angular 异步事件广播,支持多对多通信 |
挂载函数 | Angular 通过暴露 mount(container, props) 给 React 调用 |
React 主应用 <-> Angular 子应用 用 CustomEvent 通信示例
✅ 在 MFE 中使用 CustomEvent
的意义:
微前端架构中,每个子应用通常是相互隔离 、独立运行 的。它们可能是由不同团队使用不同技术栈开发的,因此需要一种轻量、技术无关的通信机制 ,而 CustomEvent
就是其中一种最佳选择。
✳️ 典型用法:主应用和子应用之间通信
🔁 从子应用向主应用发送事件
javascript
// 子应用中
const loginSuccessEvent = new CustomEvent('user-login', {
detail: { username: 'alice', token: 'abc123' }
});
window.dispatchEvent(loginSuccessEvent);
🔄 主应用监听这个事件
javascript
// 主应用中
window.addEventListener('user-login', (e) => {
console.log('收到来自子应用的登录事件:', e.detail);
// 可以更新全局状态、通知其他子应用等
});
📡 场景例子:
-
用户登录事件广播:一个子应用登录成功,主应用收到后可同步状态到其他子应用。
-
路由通知:当子应用内部路由变化,通知主应用做高亮或记录。
-
数据共享:某子应用加载了某数据,广播出去给其他依赖它的应用使用。
完整示例:
用 CustomEvent 实现 React 主应用和 Angular 子应用之间通信,核心思路就是:
-
一方通过
window.dispatchEvent(new CustomEvent('事件名', { detail: 数据 }))
触发事件 -
另一方通过
window.addEventListener('事件名', callback)
监听事件并拿到数据
1. React 主应用发送事件,接收 Angular 反馈
javascript
// React 主应用示例
import React, { useEffect, useState } from 'react';
export default function App() {
const [message, setMessage] = useState('');
useEffect(() => {
// 监听 Angular 发送的 CustomEvent
const handler = (event) => {
setMessage('收到 Angular 消息: ' + event.detail);
};
window.addEventListener('from-angular', handler);
return () => {
window.removeEventListener('from-angular', handler);
};
}, []);
// 发送事件给 Angular
const sendMessageToAngular = () => {
window.dispatchEvent(new CustomEvent('from-react', { detail: '你好,Angular!' }));
};
return (
<div>
<h1>React 主应用</h1>
<button onClick={sendMessageToAngular}>发送消息给 Angular</button>
<p>{message}</p>
<div id="angular-container" />
</div>
);
}
2. Angular 子应用监听 React 事件,发送反馈
javascript
// Angular 子应用核心代码(app.component.ts)
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h2>Angular 子应用</h2>
<button (click)="sendMessageToReact()">发送消息给 React</button>
`,
})
export class AppComponent implements OnInit, OnDestroy {
private fromReactHandler = (event: CustomEvent) => {
alert('Angular 收到 React 事件: ' + event.detail);
};
ngOnInit() {
window.addEventListener('from-react', this.fromReactHandler as EventListener);
}
ngOnDestroy() {
window.removeEventListener('from-react', this.fromReactHandler as EventListener);
}
sendMessageToReact() {
window.dispatchEvent(new CustomEvent('from-angular', { detail: '你好,React!' }));
}
}
3. Angular 应用挂载给 React 主应用调用(简要示意)
注意下面有A方式 和B方式,可根据实际情况进行选择使用
javascript
//A方式: angularApp/bootstrap.ts
export function mount(container: HTMLElement) {
// 启动 Angular 应用,把它渲染到 container
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => {
container.appendChild(document.createElement('app-root'));
});
}
//B方式
import { enableProdMode } from "@angular/core";
import { environment } from "./environments/environment";
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import "zone.js";
import { AppModule } from "./app/app.module";
if (environment.production) {
enableProdMode();
}
let appRef: any = null;
const mount = async () => {
appRef = await platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
};
const unmount = () => {
if (appRef) {
appRef.destroy();
appRef = null;
}
};
export { mount, unmount };
React 主应用调用时传入容器:
javascript
//A方式
window.angularApp.mount(document.getElementById('angular-container'));
//B方式
const [AngularComponent, setAngularComponent] = useState(null);
let unmountFunction = null;
useEffect(() => {
const loadModule = async () => {
const { mount, unmount } = await loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4200/remoteEntry.js',
remoteName: 'B方式',
exposedModule: './ReactMfeModule'});
unmountFunction = unmount;
setTimeout(() => {
setAngularComponent(mount);
setTimeout(() => {
dispatchReactHostAppData();//传递初始数据使用
}, 1000)
}, 200);
};
loadModule();
return () => {
if (unmountFunction) {
unmountFunction();
}
};
}, []);
总结
优点 | 注意事项 |
---|---|
1. 无需共享库,框架无关 | 1. 事件名要唯一,防止冲突 |
2. 传递数据简单、灵活 | 2. 事件数据建议放在 detail 字段 |
3. 浏览器原生,性能不错 | 3. 调试时注意事件监听和解绑 |
4. 适合松耦合异步通信 |