当前位置: 首页 > web >正文

微前端qiankun - 应用之间的通信

用户可能是在构建微前端架构,子应用之间需要共享数据或者触发事件。这时候通信机制就很重要了。在 qiankun 微前端架构中,子应用之间的通信可以通过以下几种方式实现。以下是详细的通信方法和示例代码:

1. 通过 window 全局对象通信

最简单的通信方式是通过全局变量或函数,但需注意 命名冲突 和 沙箱隔离 的问题:

主应用(Parent)main-app配置
qiankun-config.ts

// main-app/src/qiankun-config.ts
import { registerMicroApps, start } from 'qiankun';// 主应用中定义全局方法
(window as any).globalMethods = {sendMessage: (message: string) => {console.log('收到消息:', message);}
}// 注册子应用
registerMicroApps([{name: 'app1',entry: '//localhost:3001', // 子应用 1 地址container: '#micro-container',activeRule: '/app1',},{name: 'app2',entry: '//localhost:3002', // 子应用 2 地址container: '#micro-container',activeRule: '/app2',},
]);// 启动 Qiankun
start();

子应用(Child)app1使用全局方法

// app1/src/app.tsx
import React from 'react';
import './App.css';function App() {const handleClick = () => {// 子应用中调用全局方法(window as any).globalMethods.sendMessage('Hello From Sub App!');}return (<div className="App"><div><h1>子应用 1 - 产品展示</h1><p>这里是子应用 1 的内容。</p><button onClick={handleClick}>修改</button></div></div>);
}export default App;

在app1页面点击修改按钮
在这里插入图片描述


2. 通过事件总线(Event Bus)

利用 window 对象发布和订阅事件,实现松耦合通信(也可以使用mitt 库):

主应用(Parent)main-app作为事件总线
qiankun-config.ts文件内容

// main-app/src/qiankun-config.ts
import { registerMicroApps, start } from 'qiankun';// 2.EVENT_BUS时间总线
(window as any).EVENT_BUS = {listeners: {},on(eventName: string, callback: any) {if (!this.listeners[eventName]) this.listeners[eventName] = [];this.listeners[eventName].push(callback);},emit(eventName: string, data: any) {(this.listeners[eventName] || []).forEach((cb: any) => cb(data));}
}// 注册子应用
registerMicroApps([{name: 'app1',entry: '//localhost:3001', // 子应用 1 地址container: '#micro-container',activeRule: '/app1',},{name: 'app2',entry: '//localhost:3002', // 子应用 2 地址container: '#micro-container',activeRule: '/app2',},
]);// 启动 Qiankun
start();

子应用(Child)app1发布/订阅事件
子应用1发布事件

// app11/src/App.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';function App() {const handleClick = () => {// 替例:子应用1发布事件(window as any).EVENT_BUS.emit('user-login', { userId: 123 });}return (<div className="App"><div><h1>子应用 1 - 产品展示</h1><p>这里是子应用 1 的内容。</p><button onClick={handleClick}>修改</button></div></div>);
}export default App;

子应用2app2订阅事件

// app2/src/index.tsx
import React from 'react';
import ReactDOM, { Root } from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';// 在 module scope 保存 root 实例
let root: Root | null = null;function render(props: any) {const { container } = props;// 主应用渲染的时候,会传入一个容器id进来,有的话要优先使用,没有的话,就默认子应用独立渲染const containerDom = container? container.querySelector('#root') as HTMLElement: document.querySelector('#root') as HTMLElement;root = ReactDOM.createRoot(containerDom);root.render(<React.StrictMode><App /></React.StrictMode>);
}// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();// 子应用2订阅事件
(window as any).EVENT_BUS.on('user-login', (user: any) => {console.log('收到用户登录事件:', user);
});// 如果是在乾坤里面加载,则子应用应该跳过默认的加载逻辑,让主应用来控制当前应用的加载(匹配到子应用的路由标识)
if (!(window as any).__POWERED_BY_QIANKUN__) {render({});
}/** 暂时保持空函数 */
export async function bootstrap() {console.log('app2 bootstraped');
}/*** 挂着子应用的函数入口* @param {*} props*/
export async function mount(props: any) {console.log('app2 mount with props:', props);render(props);
}/*** 卸载子应用的函数* @param {*} props*/
export async function unmount(props: any) {// const { container } = props;if (root) {root.unmount(); // 正确卸载root = null;}
}

点击app1的修改按钮
在这里插入图片描述


3. 通过 props 传递数据

在注册子应用时,主应用可以通过 props 向子应用传递数据。

主应用(Parent)main-app传递数据
qiankun-config.ts文件内容

// main-app/src/qiankun-config.ts
import { registerMicroApps, start } from 'qiankun';// 1.主应用中定义全局方法
// (window as any).globalMethods = {
//   sendMessage: (message: string) => {
//     console.log('收到消息:', message);
//   }
// }// 2.EVENT_BUS时间总线
// (window as any).EVENT_BUS = {
//   listeners: {},
//   on(eventName: string, callback: any) {
//     if (!this.listeners[eventName]) this.listeners[eventName] = [];
//     this.listeners[eventName].push(callback);
//   },
//   emit(eventName: string, data: any) {
//     (this.listeners[eventName] || []).forEach((cb: any) => cb(data));
//   }
// }// 注册子应用
registerMicroApps([{name: 'app1',entry: '//localhost:3001', // 子应用 1 地址container: '#micro-container',activeRule: '/app1',props: { /* 可传递 Props 到子应用 */authToken: 'your_token_here',userInfo: { name: 'Alice' },}},{name: 'app2',entry: '//localhost:3002', // 子应用 2 地址container: '#micro-container',activeRule: '/app2',},
]);// 启动 Qiankun
start();

子应用(Child)app1接收数据

在子应用入口文件中,可以直接访问 props:

/*** 挂着子应用的函数入口* @param {*} props*/
export async function mount(props: any) {console.log('app1 mount with props:', props);render(props);
}

4. postMessag

通过原生 window.postMessage 实现应用间的消息传递,适用于更灵活的场景。

主应用发送事件:

// 主应用向子应用发送消息
window.postMessage({type: 'UPDATE_THEME',payload: { theme: 'dark' },},'*' // 允许跨域
);

app1子应用监听事件:

export async function mount() {window.addEventListener('message', (event) => {if (event.data.type === 'UPDATE_THEME') {const theme = event.data.payload.theme;console.log('子应用收到主题更新:', theme);}});
}

5.官方 initGlobalState 全局状态通信(双向)

主应用初始化状态:
通过 initGlobalState 初始化全局状态,并暴露 actions 对象用于操作状态

actions 对象,包含以下方法:

  • setGlobalState:更新全局状态。
  • getGlobalState:获取当前全局状态。
  • onGlobalStateChange:监听全局状态的变化。
  • offGlobalStateChange:取消监听全局状态的变化。

主应用监听状态变化:
通过 onGlobalStateChange 监听全局状态的变化,当状态被修改时执行回调。

主应用或子应用修改状态:
通过 setGlobalState 更新状态,触发所有监听器(包括主应用和子应用的监听)。

主应用初始化全局状态
新建 main-app/src/shared/actions.ts

// main-app/src/share/actions.ts
import { initGlobalState } from 'qiankun';// 主应用中注册全局事件
// 设置全局状态
const initialState = {userInfo: { name: 'Alice', age: 25 },
};
// 创建全局状态实例并导出
const actions = initGlobalState(initialState);
// 监听全局状态变化(可选)
actions.onGlobalStateChange((newState, prev) => {console.log('主应用监听到全局状态变化:', newState, prev);
});
export default actions;

注册子应用时传递状态‌
main-app/src/qiankun-config.ts

// main-app/src/qiankun-config.ts
import { registerMicroApps, start } from 'qiankun';import actions from './share/actions'// 1.主应用中定义全局方法
// (window as any).globalMethods = {
//   sendMessage: (message: string) => {
//     console.log('收到消息:', message);
//   }
// }// 2.EVENT_BUS时间总线
// (window as any).EVENT_BUS = {
//   listeners: {},
//   on(eventName: string, callback: any) {
//     if (!this.listeners[eventName]) this.listeners[eventName] = [];
//     this.listeners[eventName].push(callback);
//   },
//   emit(eventName: string, data: any) {
//     (this.listeners[eventName] || []).forEach((cb: any) => cb(data));
//   }
// }// 注册子应用
registerMicroApps([{name: 'app1',entry: '//localhost:3001', // 子应用 1 地址container: '#micro-container',activeRule: '/app1',props: { /* 可传递 Props 到子应用 */authToken: 'your_token_here',userInfo: { name: 'Alice' },// 传递全局状态操作方法onGlobalStateChange: actions.onGlobalStateChange,setGlobalState: actions.setGlobalState}},{name: 'app2',entry: '//localhost:3002', // 子应用 2 地址container: '#micro-container',activeRule: '/app2',},
]);// 启动 Qiankun
start();

主组件(main-app)触发状态更新

// main-app/src/App.tsx
import React from 'react';
import logo from './logo.svg';
import { BrowserRouter as Router, Link, Route, Routes } from 'react-router-dom';
import './App.css';import actions from './share/actions';function App() {const handleLogin = () => {// 更新全局状态(用户登录后)actions.setGlobalState({ user: { name: 'Admin' }, theme: 'dark' });  };return (<Router><div className="App"><header className="App-header"><h1>微前端容器应用</h1><button onClick={handleLogin}>login</button><nav><Link to="/app1">子应用 1</Link> | <Link to="/app2">子应用 2</Link></nav></header><main id="micro-container" style={{ minHeight: '400px' }}><Routes><Route path="/app1"  element={<div>加载中...</div>} >{/* <div>加载中...</div> */}</Route><Route path="/app2"  element={<div>加载中...</div>} >{/* <div>加载中...</div> */}</Route><Route path="/"  element={<div>请选择一个子应用进行访问</div>} >{/* <div>请选择一个子应用进行访问</div> */}</Route></Routes></main></div></Router>);
}export default App;

子应用接入(React 18+)
‌入口文件导出生命周期,并且通过contextactions方法注入所有组件‌
app1/src/index.tsx 中:

// app1/src/index.tsx
import React, { createContext } from 'react';
import ReactDOM, { Root } from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';interface IGlobalActions {setGlobalState?: (state: any) => void;onGlobalStateChange?: (callback: Function, fireImmediately?: boolean) => void;
}
export const GlobalActionsContext = createContext<IGlobalActions>({});// 在 module scope 保存 root 实例
let root: Root | null = null;function render(props: any) {const { container } = props;// 主应用渲染的时候,会传入一个容器id进来,有的话要优先使用,没有的话,就默认子应用独立渲染const containerDom = container? container.querySelector('#root') as HTMLElement: document.querySelector('#root') as HTMLElement;root = ReactDOM.createRoot(containerDom);root.render(<React.StrictMode><GlobalActionsContext.Provider value={{setGlobalState: props.setGlobalState,onGlobalStateChange: props.onGlobalStateChange}}><App /></GlobalActionsContext.Provider></React.StrictMode>);
}// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();// 如果是在乾坤里面加载,则子应用应该跳过默认的加载逻辑,让主应用来控制当前应用的加载(匹配到子应用的路由标识)
if (!(window as any).__POWERED_BY_QIANKUN__) {render({});
}/** 暂时保持空函数 */
export async function bootstrap() {console.log('app1 bootstraped');
}/*** 挂着子应用的函数入口* @param {*} props*/
export async function mount(props: any) {console.log('app1 mount with props:', props);const { onGlobalStateChange, setGlobalState } = props;// 监听全局状态变化onGlobalStateChange && onGlobalStateChange((state: any, prev: any) => {console.log('[子应用] 收到新状态:', state);// 根据主题更新界面}, true);  // 立即触发一次回调 render(props);
}/*** 卸载子应用的函数* @param {*} props*/
export async function unmount(props: any) {// const { container } = props;if (root) {root.unmount(); // 正确卸载root = null;}
}

在子组件app1中触发修改

// app1/src/App.tsx
import './App.css';import { useContext } from 'react';
import { GlobalActionsContext } from './index';function App() {const handleClick = () => {// 子应用中调用全局方法//(window as any).globalMethods.sendMessage('Hello From Sub App!');// 替例:子应用1发布事件// (window as any).EVENT_BUS.emit('user-login', { userId: 123 });}const { setGlobalState } = useContext(GlobalActionsContext);const handleChangeTheme = (value: any) => {console.log(value)setGlobalState?.({ userInfo: value });};return (<div className="App"><div><h1>子应用 1 - 产品展示</h1><p>这里是子应用 1 的内容。</p><button onClick={() => handleChangeTheme({ name: 'wp', age: '18' })}>修改</button></div></div>);
}export default App;

子应用app1点击修改按钮
在这里插入图片描述

http://www.xdnf.cn/news/8951.html

相关文章:

  • 车载软件架构 -AUTOSAR Vector SIP简介
  • 线程池配置经验总结
  • 解决Visual Studio报“IntelliSense不可用,需设置TRACEDESIGNTIME = true“问题
  • 获取点击点所在区域所能容纳最大连续空白矩形面积及顶点坐标需求分析及相关解决方案
  • 【linux】全志tina分区表挂载的脚本路径
  • 绩效管理缺乏数据支持,如何提高客观性?
  • 【AI论文】QuickVideo:通过系统算法协同设计实现实时长视频理解
  • 《数据密集型应用系统设计》笔记
  • BAT32G113 发送互补PWM
  • 【位运算】比特位计数
  • 海外仓系统 选浩方WMS一款体验更好的海外仓管理系统
  • 2025年—最新ComfyUI_修复面部与手部
  • 《爱的艺术》
  • 升级Win11后VMware虚拟机屏幕调整问题
  • Redis分布式锁浅谈
  • 打羽毛球tips
  • leetcode2025. 分割数组的最多方案数-hard
  • ESP32-S3 学习笔记(2)-屏幕驱动和lvgl移植
  • 【MySQL系列】数据库死锁问题
  • TDK PC95铁氧体隔磁片的技术要求
  • uniapp中懒加载图片组件的封装与应用
  • 【Qt】QCustomPlot相关
  • 网络段、主机段、子网掩码
  • Python 学习日记 day26
  • 蓝桥杯178 全球变暖
  • 【深度解读】三一重工的数字化转型(下篇2)
  • 大数据学习(118)-SQL面试问题总结
  • @Valid和@Vlidated的区别
  • Windows安装Docker Desktop开启 Kubenetes制作并部署本地镜像
  • Java 装饰器模式(Decorator)详解​