解決vue專案中,iframe依賴onload事件傳遞訊息不可控接收問題

柳菁發表於2019-01-12

背景介紹

因專案需求,在原網站內需要嵌入另一個關聯網站的內容,很自然的想到用iframe去解決。但是進入內聯網站依賴於主網站的accessToken進行身份驗證,因此就涉及到傳遞token的方式,以及傳遞的時機這兩方面,而內聯網站的首次渲染速度過慢問題,在排除webpack打包優化這方面影響後,很有可能就是接受token不及時導致延遲渲染。

另外,這兩個都是vue專案。

先上案例程式碼

// 父頁面
<template>
  <iframe ref="framePage" @load="deliverToken"></iframe>
</template>

<script>
export default {
    computed: {
        token() {
            return token  //  簡單舉例 
        }
    },
    methods: {
        deliverToken() {
            this.refs.framePage.contentWindow.postMessage({type: 'token', data: this.token}, '*')
        }
    }
}
</script>

// 子頁面

<template>
  <div id="app"></div>
</template>
<script>
let token = null
Http() // 這個函式裡,設定了一些http請求頭資訊,以及用postMessage設定的語言、主題等資訊傳遞方法
window.addEventListener('message', function(evt) {
  const msg = evt.data
  if (msg.type !== token) { return }
  if (token !== msg.data) {
     token = msg.data
     new Vue({
       el: '#app'
     })
  }
})
</script>複製程式碼

(上述程式碼讓子專案的渲染時間在網路狀態良好情況下3-20秒內不定期發生。。。)

問題分析

兩個專案直接傳遞和接收訊息,是使用postMessage達成的。我們知道,postMessage是一個非同步方法,他的執行排在同步方法後面。postMessage只負責傳遞訊息,並不負責監聽訊息是否被接收,而是在訊息一旦傳遞就立刻給傳遞者返回成功狀態。 

問題一:token的傳遞依賴於load函式的觸發,若觸發時機不正確,會導致token獲取不到。

劃重點

原因:使用單頁面開發的Vue程式,在Vue程式碼開始執行之前就已經觸發了onload()。此時iframe中包含的是一個空的document。雖然在子專案完成掛載之後,依舊會再次觸發onload事件,但傳遞token的最佳時機已經錯過。可以想到,傳遞token的最佳時機是在頁面一掛載的時候。

問題二:子頁面是一個獨立的vue專案,在子元件掛載之前就有同步函式、專案自身的postMessage執行。父頁面傳遞的訊息需要排隊等待被接收。

原因:由於子專案中同步函式的執行,會阻塞非同步postMessage的訊息接收,此時就算傳遞了訊息也沒辦法及時獲取。

解決方案

由此可見,在vue專案中用onload事件並不靠譜(--!!,原來坑在這)

那應該何時向子專案傳遞token才能確保頁面可以正常載入呢?

由子頁面通知父頁面可以傳遞訊息了,替代onload事件

改良案例如下

// 父頁面
<template>
  <iframe ref="framePage"></iframe>
</template>

<script>
export default {
    data() {
        return {
            isChildReady: false
        }
    },
    computed: {
        token() {
            return token  //  簡單舉例 
        }
    },
    beforeMount() {
        addEventListener('message', function (evt) {
            if (evt.data.type !== 'childStatus') { return }
            this.isChildReady = evt.data.data
        })
    },
    methods: {
        deliverToken() {
            this.refs.framePage.contentWindow.postMessage({type: 'token', data: this.token}, '*')
        }
    },
    watch: {
        isChildReady(isReady) {
            if (!isReady) { return }
            this.deliverToken()
        }
    }
}
</script>

// 子頁面

<template>
  <div id="app"></div>
</template>
<script>
let token = null
Http() // 這個函式裡,設定了一些http請求頭資訊,以及用postMessage設定的語言、主題等資訊傳遞方法
window.parent.postMessage({type: 'childStatus', data: 'isReady'}, '*')
window.addEventListener('message', function(evt) {
  const msg = evt.data
  if (msg.type !== token) { return }
  if (token !== msg.data) {
     token = msg.data
     new Vue({
       el: '#app'
     })
  }
})
</script>複製程式碼

當子專案的根節點掛載後,postMessage通知父專案傳遞token

其一,避免了onload事件觸發不準確的問題;

第二,避免訊息佇列的等待導致的接收不及時問題。

最終結果,子專案的渲染時間在網路狀態良好情況下,被控制在0-2秒內。

以上就是本週遇到並解決的vue中iframe內嵌專案,由於postMessage接收不到導致的首頁載入過慢的問題。除了postMessage本身的無法監聽是否接收到訊息問題之外,還有onload事件在Vue專案中的問題。


相關文章