????????在 微前端(MFE, Micro Frontends) 中使用 CustomEvent?是非常常見的,尤其是在不同子應用(Micro Apps)之間通信的時候。今天將以React 主應用 ? Angular 子應用 之間的通信進行示例
React 主應用 <-> Angular 子應用 用 mitt(事件總線) 通信示例
1. React 主應用
-
通過 props 給 Angular 子應用傳遞數據和回調函數
-
使用 mitt(事件總線)進行異步事件廣播
// 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.mountwindow.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 發來的事件
// 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
就是其中一種最佳選擇。
?? 典型用法:主應用和子應用之間通信
🔁 從子應用向主應用發送事件
// 子應用中
const loginSuccessEvent = new CustomEvent('user-login', {detail: { username: 'alice', token: 'abc123' }
});window.dispatchEvent(loginSuccessEvent);
🔄 主應用監聽這個事件
// 主應用中
window.addEventListener('user-login', (e) => {console.log('收到來自子應用的登錄事件:', e.detail);// 可以更新全局狀態、通知其他子應用等
});
📡 場景例子:
-
用戶登錄事件廣播:一個子應用登錄成功,主應用收到后可同步狀態到其他子應用。
-
路由通知:當子應用內部路由變化,通知主應用做高亮或記錄。
-
數據共享:某子應用加載了某數據,廣播出去給其他依賴它的應用使用。
完整示例:
用 CustomEvent 實現 React 主應用和 Angular 子應用之間通信,核心思路就是:
-
一方通過
window.dispatchEvent(new CustomEvent('事件名', { detail: 數據 }))
觸發事件 -
另一方通過
window.addEventListener('事件名', callback)
監聽事件并拿到數據
1. React 主應用發送事件,接收 Angular 反饋
// React 主應用示例import React, { useEffect, useState } from 'react';export default function App() {const [message, setMessage] = useState('');useEffect(() => {// 監聽 Angular 發送的 CustomEventconst handler = (event) => {setMessage('收到 Angular 消息: ' + event.detail);};window.addEventListener('from-angular', handler);return () => {window.removeEventListener('from-angular', handler);};}, []);// 發送事件給 Angularconst 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 事件,發送反饋
// 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方式,可根據實際情況進行選擇使用
//A方式: angularApp/bootstrap.tsexport function mount(container: HTMLElement) {// 啟動 Angular 應用,把它渲染到 containerplatformBrowserDynamic().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 主應用調用時傳入容器:
//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. 適合松耦合異步通信 |