我们使用过vue,了解两种常用的通信模式:观察者和发布 / 订阅模式。两者最主要的区别是一对多单向通信还是多对多双向通信的问题。使用我们要确定多个子应用之间互相是否需要通信,来决定使用哪种通信方式。

在微前端中往往需要实现多对多的双向通信模式,例如微应用之间实现通信,主应用和微应用之间实现通信,因此使用发布 / 订阅模式是一种不错的选择。

因为要克服不在同一执行的上下文与跨域问题,我们选择postMessage 实现跨域通信。

demo案例如下

// 主应用
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>主应用</title>
  </head>
  <body>
    <br />
    <iframe id="micro1" src="<%= micro1 %>"></iframe>
    <iframe id="micro2" src="<%= micro2 %>"></iframe>

    <script>
      const micro1 = document.getElementById("micro1");
      const micro2 = document.getElementById("micro2");

      // 等待 iframe 加载完毕后才能通信
      micro1.onload = () => {
        // 给子应用发送消息,注意明确 targetOrigin
        micro1.contentWindow.postMessage("main", "<%= micro1 %>");
      };

      micro2.onload = () => {
        micro2.contentWindow.postMessage("main", "<%= micro2 %>");
      };

      // 接收来自于 iframe 的消息
      window.addEventListener("message", (data) => {
        // 通过 data.origin 来进行应用过滤
        if (
          data.origin === "<%= micro1 %>" ||
          data.origin === "<%= micro2 %>"
        ) {
          console.log("main: ", data);
        }
      });
    </script>
  </body>
</html>
<!-- micro1.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>micro1</title>
  </head>
  <body>
    <h1>micro1</h1>
    <script>
      window.addEventListener("message", (data) => {
        console.log("micro1: ", data);

        if (data.origin === "<%= mainUrl %>") {
          window.parent.postMessage("micro1", "<%= mainUrl %>");
        }
      });
    </script>
  </body>
</html>
<!-- micro2.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>micro1</title>
  </head>
  <body>
    <h1>micro1</h1>
    <script>
      window.addEventListener("message", (data) => {
        console.log("micro2: ", data);

        if (data.origin === "<%= mainUrl %>") {
          window.parent.postMessage("micro2", "<%= mainUrl %>");
        }
      });
    </script>
  </body>
</html>

以下浓缩操作即为ifream基础通信

父传子

// 等待 子 加载完毕后才能通信
// 父操作
子.contentWindow.postMessage("消息", "指定哪些窗口能接收到消息事件");
// 子操作
window.addEventListener("message", (data) => {
	console.log("micro1: ", data);
}

子传父

// 子操作
window.parent.postMessage("消息", "指定哪些窗口能接收到消息事件")
// 父操作
window.addEventListener("message", (data) => {
	if (data.origin === "指定哪些窗口能接收到消息事件") {
		console.log("main: ", data);
	}
}

未曾清贫难成人,不经打击老天真。 自古英雄出炼狱,从来富贵入凡尘。 醉生梦死谁成气,拓马长枪定乾坤。 挥军千里山河在,立名扬威传后人。