用户可能是在构建微前端架构,子应用之间需要共享数据或者触发事件。这时候通信机制就很重要了。在 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点击修改按钮
在这里插入图片描述

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐