背景介紹
因專案需求,在原網站內需要嵌入另一個關聯網站的內容,很自然的想到用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專案中的問題。