天天看点

【前端跨域】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解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

从网上看到的大多数基于

postMessage

跨域的教程基本上都是这个流程。

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

为什么呢?请往下看:

我们进行

debug

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

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

【前端跨域】postMessage解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

改善方案

通过

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

可以看到当我们准备从父页面发送数据时,子页面已经渲染出来。

【前端跨域】postMessage解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

因此,接下来我们就拿到了父页面的数据:

【前端跨域】postMessage解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

在实际工作的项目中,

React

界面往往没有这么简单,页面需要加载的东西很多。

在实际工作中用上面的方案可能是下面的结果:

【前端跨域】postMessage解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

所以,用这种方案,我们在子页面还是不能拿到数据。

寻求其他方案

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

3000ms

// PostMessageVueSide.vue
mounted() {
    console.log('mounted');
    setTimeout(()=>{
      this.$refs.parentPage.contentWindow.postMessage(this.message, '*');
    }, 3000);
}
           
【前端跨域】postMessage解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

这种方案是可行的,数据是能拿到。

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

这种方案的缺陷很明显,我们不确定子页面究竟要多少

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解决iframe页面跨域问题的最佳实践关键技术最佳实践总结源码

完美解决了问题。

展望方案

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

我们可以设计一个

顶层数据点

,让父页面和子页面都从这个

顶层数据点

拿数据及修改数据等。这样可以保证父子界面拿到的数据是同步的。

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

总结

postMessage

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

源码

  • https://gitee.com/LynnHg/best-practice-demo

继续阅读