【前端跨域】postMessage解决iframe页面跨域问题的最佳实践

背景:有两个项目,一个基于Vue开发,一个基于React开发。Vue项目中某个页面(称为父页面)通过iframe标签嵌入了React项目的某个页面(称为子页面)。现在子页面要和父页面要进行通信,传递数据。

关键技术

跨域方式还挺多的,本次主要通过H5提供的postMessage()方法解决上面提到的问题。

最佳实践

初步方案

  • Vue 父页面核心代码
// PostMessageVueSide.vue
<template>
  <div class="container border-2 border-gray-400 border-solid">
    <h1>这是基于Vue的页面(父页面)</h1>
    <p><span class="text-red-500">传递的数据:</span>{{message}}</p>
    <div class="w-full h-full">
      <iframe class="w-full h-full"  ref="parentPage" scrolling="no" src="http://localhost:3001"></iframe>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      message: 'vue page data'
    }
  },
  mounted() {
    this.$refs.parentPage.contentWindow.postMessage(this.message, '*');
  }
}
</script>

<style lang="scss" scoped>
.container {
  min-height: calc(100vh - 100px);
}
</style>

基于Vue的项目启动服务后的地址在http://localhost:8080/

http://localhost:3001是基于React的项目启动服务后的地址。

当页面挂在完成,我们通过$fefs获取iframe实例,并通过contentWindow.postMessage向子页面发送数据。

  • React 子页面核心代码
// PostMessageReactSide.jsx
import React, {useEffect, useState} from 'react';

import './Home.scss'

const Home = () => {
  const [message, setMessage] = useState('');

  useEffect(() =>{
    window.addEventListener('message', (e) =>{
      console.log(e.data);
      setMessage(e.data);
    })
   }
  );
  
  return (
    <div className="container">
      <p>这是基于React的页面(子页面)</p>
      <p><span className="received-data">收到的数据:</span>{message}</p>
    </div>
  )
}

export default Home;

React界面基于React Hook实现。

useEffect函数中我们监听message事件,并试图获取从父页面发来的数据。

  • 界面效果
    在这里插入图片描述

从网上看到的大多数基于postMessage跨域的教程基本上都是这个流程。

但是,从页面效果可看到,我们的子页面并没有拿到从父页面拿到数据。

为什么呢?请往下看:

我们进行debug,可以看到当我们准备要向子页面发送数据时,此时子页面还没加载出来。

因此,如果我们想要在子页面拿到父页面的数据,必须等子页面加载完成后,父页面再向子页面发送数据。
在这里插入图片描述

改善方案

通过load事件,在页面加载完成时,再发送数据。

// PostMessageVueSide.vue
 mounted() {
    console.log('mounted');
    const parentPage = this.$refs.parentPage;
    parentPage.addEventListener('load', ()=> {
      parentPage.contentWindow.postMessage(this.message, '*');
    })
    // this.$refs.parentPage.contentWindow.postMessage(this.message, '*');
  }

通过debug可以看到当我们准备从父页面发送数据时,子页面已经渲染出来。
在这里插入图片描述
因此,接下来我们就拿到了父页面的数据:
在这里插入图片描述
在实际工作的项目中,React界面往往没有这么简单,页面需要加载的东西很多。

在实际工作中用上面的方案可能是下面的结果:
在这里插入图片描述
所以,用这种方案,我们在子页面还是不能拿到数据。

寻求其他方案

我们还可以用定时器来延时发送数据。这里我们延时设置为3000ms

// PostMessageVueSide.vue
mounted() {
    console.log('mounted');
    setTimeout(()=>{
      this.$refs.parentPage.contentWindow.postMessage(this.message, '*');
    }, 3000);
}

在这里插入图片描述
这种方案是可行的,数据是能拿到。

但因为子页面加载较快,过了一会儿数据才显示。

这种方案的缺陷很明显,我们不确定子页面究竟要多少ms才能加载完成。

终极方案

既然上面我们不确定子页面究竟要多少ms才能加载完成。那我们可以等子页面加载完成时向父页面发送一个信号,告诉父页面“我已经加载完成,你可以向我发送数据了”。

那这里我们就需要从子页面通过postMessage向父页面发送数据了。

在子页面中我们在useEffect中发送数据通知父页面,子页面已加载完成。

// PostMessageReactSide.jsx
const Home = () => {
  const [message, setMessage] = useState('');

  useEffect(() => {
    // 页面加载已完成,通知父页面
    console.log('child page mounted');
    window.parent.postMessage({retcode: 200}, '*');   
  });

  useEffect(() =>{
    window.addEventListener('message', (e) =>{
      console.log(e.data);
      setMessage(e.data);
    })
   }
  );
  
  return (
    <div className="container">
      <p>这是基于React的页面(子页面)</p>
      <p><span className="received-data">收到的数据:</span>{message}</p>
    </div>
  )
}

父页面中监听message,当拿到子页面的数据时(说明子页面已加载完成),我们就向子页面发送数据。

// PostMessageVueSide.vue
  mounted() {
    console.log('parent page mounted');
    window.addEventListener('message', (e) => {
      if (e.data.retcode === 200) {
        this.$refs.parentPage.contentWindow.postMessage(this.message, '*');
      }
    })
  }

刷新页面:
在这里插入图片描述
完美解决了问题。

展望方案

父子页面来回传递数据容易造成数据混乱,难以管理,数据流不明确。

我们可以设计一个顶层数据点,让父页面和子页面都从这个顶层数据点拿数据及修改数据等。这样可以保证父子界面拿到的数据是同步的。

例如,父子页面都可以从后台调统一接口拿数据,而不用让数据在父子页面间来回传递。

总结

postMessage解决跨域问题最关键的点就是,父页面要在子页面加载完成时再发送数据,否则子页面可能收不到数据。

源码

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
在 Web 开发中,当网页中包含来自不同名的 iframe(内嵌框架)时,由于浏览器的同源策略限制,iframe 之间的直接通信会受到限制。为了实现 iframe 之间的通信,可以使用 postMessage 方法。 postMessage 是一种 HTML5 提供的文档消息传递机制,它允许在不同窗口或 iframe 之间发送消息。通过 postMessage,可以在不同名之间进行安全的双向通信。 使用 postMessage 进行通信的步骤如下: 1. 在发送消息的页面(发送方)中,使用 JavaScript 调用 postMessage 方法发送消息。该方法接受两个参数:要发送的消息和接收消息的目标窗口的源(origin)。目标窗口的源可以是具体的名、协议和端口号,或者是通配符 "*" 表示任意源。 ```javascript var targetWindow = document.getElementById('target-frame').contentWindow; targetWindow.postMessage('Hello', 'https://target-domain.com'); ``` 2. 在接收消息的页面(接收方)中,监听 message 事件,通过 event.data 获取接收到的消息。在事件处理程序中可以对消息进行处理。 ```javascript window.addEventListener('message', function(event) { if (event.origin === 'https://source-domain.com') { console.log('Received message: ' + event.data); } }); ``` 通过这种方式,发送方和接收方可以进行通信,并且可以在消息中传递复杂的数据结构。但要注意,为了确保安全性,应该在接收方对来自不同源的消息进行验证,以防止恶意代码的攻击。 需要注意的是,postMessage 方法只能在现代浏览器中使用,兼容性可能会有所差异。此外,在使用 postMessage 进行通信时,也需要确保目标窗口(接收方)支持 postMessage 方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值