前回、Reactで作ったWebアプリ(以下、React アプリ)に、Vueで作ったWebアプリ(以下、Vue アプリ)をReact Iframeを用いて合体させました。React アプリからVue アプリに値を渡す必要があったので、今回は値を渡す方法についてまとめました。
やったこと
URLパラメータとpostMessageの2つの方法を試してみました。
今回は、ユーザー名のaaa.bbb
をReact アプリからVue アプリに渡して、Vueのページで表示したいと思います。
URLパラメータ
URLの末尾にデータを付与して送ります。
Reactプロジェクト
Iframeに指定するURLのパラメータとして、?userName=aaa.bbb
を加えます。
import React from 'react';
import Iframe from 'react-iframe'
export default function Page1(){
return (
<div>
<Iframe id = 'page1'
url = '<埋め込みたいサイトURL>?userName=aaa.bbb'
position='absolute'
width='80%'
height='90%'/>
</div>
);
}
Vueプロジェクト
Vue Routerのインストール
URLパラメータで送られた情報をVueで取得するには、Vue Routerを使います。Vue Routerの使い方はこちらを参考にさせていただきました。
VueプロジェクトにVue Routerをインストールします。
$ yarn add vue-router
router.jsの作成
ルーティングを管理するためのrouter.js
ファイルを作成します。デフォルトはhashモードになっているのですが、hashモードだと値が取れなかったので、historyモードを指定しています。
import Vue from 'vue'
import Router from 'vue-router'
import App from './App';
Vue.use(Router);
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'App',
component: App
}
]
});
main.jsに追記
main.js
にも前述で作成したrouter.js
をインポートし、 Vue インスタンス作成処理に、router
をインジェクトします。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
値を使う
URLパラメータの値はthis.$route.query.${キー}
で取得することができます。
<template>
<div id='app'>
<h1>{{ userName }}</h1>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userName: 'ユーザー名'
}
},
mounted: function(){
this.userName = this.$route.query.userName
}
}
</script>
これで、URLパラメータ
で情報を渡すことができました!
postMessage
postMessage
を用いてデータを送ります。
Reactプロジェクト
Iframeの属性にonLoad
を追加します。そうすると画像を含む画面の読み込みが完了した時に、指定しているloaded
関数が実行されます。
postMessage
についてはこちらを参考にしました。今回第二引数には、送り先(埋め込みたいサイト)のURLを指定してましたが、送り先を限定したくない場合は*
を指定することもできます。
import React from 'react';
import Iframe from 'react-iframe'
export default function Page1(){
const loaded = () => {
const ifrm = document.getElementById('page1').contentWindow;
ifrm.postMessage({
userName: 'aaa.bbb',
}, '<埋め込みたいサイトURL>');
}
return (
<div>
<Iframe id = 'page1'
url = '<埋め込みたいサイトURL>'
position='absolute'
width='80%'
height='90%'
onLoad={loaded}/>
</div>
);
}
(余談)
React Iframeのページには、onLoad
の記載がなく、ここに辿り着くまで結構時間がかかってしまいました。
Vueプロジェクト
メッセージ受信時に実行されるwindow.addEventListener
のタイプmessage
を使用して、Reactから送られてくる値をキャッチします。
また、送った値とともに送信元のオリジンも取得できるので、送信元オリジンのチェックも行っています。
<template>
<div id='app'>
<h1>{{ userName }}</h1>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
userName: 'ユーザー名'
}
},
mounted: function(){
const self = this
const accessURL = '<送信元URL>'
window.addEventListener('message', function(event) {
if(event.origin === accessURL){
self.userName = event.data.userName
}
}, false);
}
}
</script>
これで、postMessage
で値を渡すことができました!
つまずいたいたところ
React アプリからVue アプリに値を渡すためのpostMessage
を実行するタイミングにつまずいたので、失敗した実装方法も残しておきます。
つまずき① React Routerの遷移では反応してくれない問題
// 失敗例
window.onload = function() {
const ifrm = document.getElementById('page1').contentWindow;
ifrm.postMessage({
userName: 'aaa.bbb',
}, '<埋め込みたいサイトURL>');
};
postMessage
について参考にしたサイトに記載している値の受け取り方をそのまま適用してみました。リロードをすると意図した通りに動作しますが、React Routerの遷移では、window.onload
は動きません。。
つまずき② 埋め込んだサイトの読み込みが終わっていない問題
// 失敗例
useEffect(() => {
const ifrm = document.getElementById('page1').contentWindow;
ifrm.postMessage({
userName: 'aaa.bbb',
}, '<埋め込みたいサイトURL>');
}, []);
つまずき①の結果から、コンポーネントのマウント時に実行されるようにuseEffect
を使用してみました。もちろんこれだと、React Routerの遷移でもリロードでも動きますが、おそらく動き出したタイミングでは埋め込んだサイトの読み込みが終わっておらず、Vue アプリ側で値を受け取ることができませんでした。
なので、例えばuseEffect
内で1秒待機をしてからpostMessage
の処理が動き出すように実装すると、問題なく動作はしますが、待機か・・という気持ちになります。
その他、addEventListener
のpageshow
やpopstate
を使ってみたりしましたが、ことごとく上手くいかなかったです。
おわりに
埋め込んだサイトに値を渡す方法としてURLパラメータ
とpostMessage
という2つの方法についてまとめました。
実際に渡したかった値がセキュリティの観点からURLパラメータを使用するのは相応しくない値だったので、postMessageを使用するようにしました。ただ、同時に実行すると、URLパラメータのほうがややはやくReact アプリからVue アプリに値を渡せることがわかったので(後述)、セキュリティ的に問題ない値であれば、URLパラメータを使用するのもいいのかなと思いました。
また、今回はReact Routerを使ってVueで作ったWebアプリに飛びたいという要望があったこともあり、postMessage
は実行タイミングを使いこなすのに時間がかかってしまいました。。
みなさん、こんなときはonLoad
を使いましょう!他に良い方法がありましたら、コメントください!
おまけ 実行速度について
同時にするとどっちがはやいか選手権もやってみました。
Reactプロジェクト
import React from 'react';
import Iframe from 'react-iframe'
export default function Page1(){
const loaded = () => {
const ifrm = document.getElementById('page1').contentWindow;
ifrm.postMessage({
userName: 'aaa.bbb',
}, '<埋め込みたいサイトURL>?userName=aaa.bbb');
}
return (
<div>
<Iframe id = 'page1'
url = '<埋め込みたいサイトURL>?userName=aaa.bbb'
position='absolute'
width='80%'
height='90%'
onLoad={loaded}/>
</div>
);
}
Vue プロジェクト
// 省略
<script>
export default {
name: 'App',
data() {
return {
userNameUrl : '',
userNamePost : ''
}
},
watch: {
userNameUrl: function () {
console.log(this.getTime() + ' URLパラメータ')
},
userNamePost: function () {
console.log(this.getTime() + ' postMessage')
}
},
mounted: function(){
const self = this
const accessURL = '<送信元URL>'
window.addEventListener('message', function(event) {
if(event.origin === accessURL){
self.userNamePost = event.data.userName
}
}, false);
this.userNameUrl = this.$route.query.userName
}
}
// 現在時刻の取得省略
</script>
実行結果
22:32:05:763 URLパラメータ
22:32:05:778 postMessage
URLパラメータの方が若干はやいことがわかりました。URLパラメータだと値を送るのに、埋め込んだサイトの読み込みを待たなくていいからですかね。