微前端qiankun - 应用之间的通信
用户可能是在构建微前端架构,子应用之间需要共享数据或者触发事件。这时候通信机制就很重要了。在 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+)
入口文件导出生命周期,并且通过context
将actions
方法注入所有组件
在 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
点击修改按钮
更多推荐
所有评论(0)