Skip to content

iframe 作为微前端的实现问题处理

通信与数据共享

在微前端架构中,通信与数据共享是关键。可以封装一个公共方法来实现统一处理。

首先在主应用中创建一个 IFrameCommunication 类,用于处理 iframe 通信。

typescript
// iframe-communication.ts

interface IFrameMessage {
  type: string;
  payload: any;
}

export class IFrameCommunication {
  private iframe: HTMLIFrameElement;
  private messageListeners: Map<string, (message: IFrameMessage) => void>;

  constructor(iframe: HTMLIFrameElement) {
    this.iframe = iframe;
    this.messageListeners = new Map();
    this.init();
  }

  private init() {
    window.addEventListener('message', this.handleMessage.bind(this));
  }

  private handleMessage(event: MessageEvent) {
    // 验证消息来源
    if (event.origin !== this.iframe.origin) {
      return;
    }

    const message: IFrameMessage = event.data;
    if (this.messageListeners.has(message.type)) {
      this.messageListeners.get(message.type)?.(message);
    }
  }

  // 向 iframe 发送消息
  public sendMessage(type: string, payload: any) {
    this.iframe.contentWindow?.postMessage({ type, payload }, this.iframe.src);
  }

  // 监听来自 iframe 的消息
  public onMessage(type: string, listener: (message: IFrameMessage) => void) {
    this.messageListeners.set(type, listener);
  }

  // 移除消息监听
  public offMessage(type: string) {
    this.messageListeners.delete(type);
  }
}

然后创建一个hooks

typescript
// useIFrameCommunication.ts

import { ref, onMounted, onBeforeUnmount } from 'vue';
import { IFrameCommunication } from './iframe-communication';

export function useIFrameCommunication(iframeRef: any) {
  const iframeComm = ref<IFrameCommunication | null>(null);

  // 初始化通信
  onMounted(() => {
    if (iframeRef.value) {
      iframeComm.value = new IFrameCommunication(iframeRef.value);
    }
  });

  // 清理资源
  onBeforeUnmount(() => {
    iframeComm.value?.offMessage('init');
  });

  // 发送消息到 iframe
  const sendMessageToIFrame = (type: string, payload: any) => {
    iframeComm.value?.sendMessage(type, payload);
  };

  // 接收来自 iframe 的消息
  const onMessageFromIFrame = (type: string, callback: (message: any) => void) => {
    iframeComm.value?.onMessage(type, callback);
  };

  return {
    sendMessageToIFrame,
    onMessageFromIFrame,
  };
}

在主应用中使用

vue
<template>
  <div>
    <iframe ref="iframe" src="https://your-iframe-url.com" width="600" height="400"></iframe>
    <button @click="sendToIframe">Send Message to Iframe</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
import { useIFrameCommunication } from './useIFrameCommunication';

export default defineComponent({
  name: 'IframeComponent',
  setup() {
    const iframeRef = ref<HTMLIFrameElement | null>(null);
    const { sendMessageToIFrame, onMessageFromIFrame } = useIFrameCommunication(iframeRef);

    onMounted(() => {
      // 监听来自 iframe 的消息
      onMessageFromIFrame('routeChange', (message) => {
        console.log('Received from iframe:', message.payload);
      });
    });

    const sendToIframe = () => {
      sendMessageToIFrame('changeRoute', {
        path: '/about',
      });
    };

    return {
      iframeRef,
      sendToIframe,
    };
  },
});
</script>

在自应用中也是一样,不过建议把消息的 type 和 payload 的类型定义在一个公共的地方,方便维护。

路由保持,比如刷新路由, iframe 也能保持当前路由

在子应用中,监听路由变化,然后发送消息到主应用,主应用接收到消息后,可以更新 path 以及 缓存当前路由,刷新后再次加载 iframe 时,可以根据缓存的路由来初始化 iframe。

typescript
import { onMounted } from 'vue';
import { useIFrameCommunication } from './useIFrameCommunication';

export default {
  setup() {
    const iframeRef = ref<HTMLIFrameElement | null>(null);
    const { sendMessageToIFrame, onMessageFromIFrame } = useIFrameCommunication(iframeRef);

    onMounted(() => {
      watch(
        () => currentRoute,
        (newRoute) => {
          sendMessageToIFrame('routeChange', { payload: { path: newRoute.path } });
        },
      );
    });
  },
};

主应用中监听来自 iframe 的消息,然后根据消息更新路由。

typescript
onMessageFromIFrame('changeRoute', (message) => {
  // 拿到 path, localStorage.setItem('currentRoute', message.payload.path);
  localStorage.setItem('currentRoute', message.payload.path);

  // 利用 history.pushState 来更新路由
  history.pushState(null, null, message.payload.path);
});

// 刷新页面后,根据缓存的路由来初始化 iframe

遮罩层的问题

因为子应用的遮罩层只能显示在子应用的范围内。 解决办法就是子应用打开有遮罩层的比如 modal 时,发送消息到主应用,主应用显示一个全局的遮罩层。

需要注意的是层级问题,主应用的遮罩层要比 iframe 的层级高。所以需要把iframe的层级设置比遮罩层高。