iframe通信

477 阅读2分钟
背景

最近做项目来了一个需求,需要对一个复杂系统简化流程所以想法是利用一个菜单类型的外壳去包裹住里面的复杂的系统来简化使用人的操作流程,大概实现的方式是利用iframe去嵌套这个系统然后双方约定命令式消息通信实现这个项目的交互。

通信方式

主要是利用到h5的window.postMessage这个支持跨源通信api进行通信的操作,实现双方的交互。

简单示例
// 外壳发送信(http://localhost:8000)
function postMsg() {
    var myIframe = document.getElementById("inlineFrameExample");
    if (myIframe) {
        var data = JSON.stringify({
            msg: '传递信息数据'
        }) myIframe.contentWindow.postMessage(data, "http://localhost:8080");

    }
}

function acceptMessage(event) {

 if (event.origin !== "http://localhost:8080")
    return;
    let data = event.data;
}

window.addEventListener("message", acceptMessage, false);

// iframe内部接收消息(http://localhost:8080)
function receiveMessage(event) {
    if (event.origin !== "http://localhost:8000") return;
    // event.source 是我们通过window.open打开的弹出页面
    // event.data 是发送给当前页面的消息
    var data = JSON.parse(event.data);

   // 反馈消息回去
  event.source.postMessage("我已经收到消息了!",event.origin);

}
window.addEventListener("message", receiveMessage, false);

项目实战

由于项目里面肯定涉及大量的通信的数据,所以肯定我们需要对于通信的方式进行规范和封装

   /** 发送信息 */
    postMessage({ api, params }) {
      return new Promise((resolve, reject) => {
        const objRequest = {
          require: {
            api: api,
            params: params,
            uuid: uuid()
          }
        }
        // 双方可以通过objRequest进行约定通信的一些规则一般来说有回调函数名称和参数,以及后面提到为了确认接收信息而建立的队列
        this.$refs.iframe.contentWindow.postMessage(objRequest, '*')
      })
    },

/** 监听事件 */
    handleReceive(e) {
      const { api, type, params, uuid, appName } = e
      const method = this[api]
      if (!method) return
      // 通过监听来回调函数获取接收信息
      method.apply(this, params ? [params] : [])
    },

对于我们的项目来说由于通信机制较为频繁所以抽离一个js文件然后混入到相关文件

export default {
  methods: {
  // 通过监听事件回调,我就可以在这里拿到数据了
    test(data) {
      console.log('监听到的消息', data)
      // 通常分发的消息需要经过各个组件,所以用全局bus去分发数比较合理
      this.$bus.$emit('busEvent', data)
    }
   }
 }

然后我们可以经过封装在各个组件接收信息的时候建立一个监听响应的机制

function bindVue(handlers) {
  const handlersWithVue = {}
  return {
    created() {
      for (const [event, _handler] of Object.entries(handlers)) {
        // 把_handler的this绑定到vue调用环境中去
        const handler = _handler.bind(this) 
        this.$bus.$on(event, handler)
        // 依赖收集为销毁做处理
        handlersWithVue[event] = handler
      }
    },

    beforeDestroy() {
      for (const [key, value] of Object.entries(handlersWithVue)) {
        this.$bus.$off(key, value)
      }
    }
  }
}
export default bindVue

// 页面使用方式
 mixins: [
 // 其实组件的本质就是函数
    busEvent({
      setTest(data) {
        this.isTest = data
      }
    })
  ],

然后承接上文说道的队列,也许你会思考为什么我们还需建立一个异步队列,我的回答是,某些时候我们发出的信息,对方不一定能够接收之后及时给出反馈因为各种原因,这个时候我们可以建立一个异步队列来控制【连接】超时时间而且能够对异常结果进行捕获然后抛出,就像是请求一样

/**
 * 异步队列
 * @param time - 超时时间,单位秒
 */
export default class QueuePromise {
  constructor(time = 15) {
    this.queue = new Map()
    this.time = time
  }

  // 加入队列
  push(uuid, resolve, reject) {
    const params = { uuid, resolve, reject }
    params.timer = setTimeout(() => {
      this.put(uuid)
      reject({ code: -1, msg: '超时' })
    }, this.time * 1000)
    this.queue.set(uuid, params)
  }

  // 执行队列,并出栈
  perform(uuid, params) {
    const apiQueue = this.put(uuid)
    if (!apiQueue) return
    apiQueue.resolve(params)
  }

  // 找到并出栈
  put(uuid) {
    const apiQueue = this.queue.get(uuid)
    if (!apiQueue) return
    this.queue.delete(uuid)
    apiQueue.timer && clearTimeout(apiQueue.timer)
    return apiQueue
  }

  clear() {
    this.queue.forEach(e => {
      e.timer && clearTimeout(e.timer)
    })
    this.queue = null
  }
}

综上基本就能在项目里面建立一套简易的通信机制来运用在跨域通信使用上面

参考链接

www.jianshu.com/p/6be6c3f28…