[ReactNative] WebViewとアプリでデータをやりとりする
前提
- react-native-webviewライブラリを使用
-
react-native
:0.72.10
WebViewで表示している画面の操作をアプリでハンドリングしたい
ネイティブアプリケーションを開発していると、どうしてもWebViewを使用せざるを得ないことって多くないでしょうか?例えば、ECのアプリで決済周りの部分がAPI開発すると色々面倒なので、WebViewでそのまま表示させるとか、スクラッチで作ると工数がかかるから一部分はWebViewにする...などなど。
そんな時に、「WebView内でのイベントをキャッチしてアプリ側でハンドリングしたい」なんてことがしばしばあるかと思います。(モーダル出すとか、アプリの画面に遷移させるとか)
今回はそんな時に、どのようにしてやりとりを行うか説明していきます。
流れ
-
react-native-webview の
<WebView />
コンポーネントのinjectedJavaScript propsでWebView内で実行したいJavaScriptを記述。 - そのJavaScriptの中で
window.ReactNativeWebView.postMessage()
する。 - アプリ側では
onMessage={}
propsでWebView内からのpostMessageを受け取る。 - onMessageでアプリでやりたい処理を記述
実装
完成系は以下です。
import React from 'react';
import { StyleSheet, View } from 'react-native';
import WebView, { WebViewMessageEvent } from 'react-native-webview';
export const SampleScreen = () => {
const injectedJavaScript = `
window.ReactNativeWebView.postMessage(
JSON.stringify({
type: 'postTest',
})
);
`;
const _onMessage = async (event: WebViewMessageEvent) => {
const data = JSON.parse(event.nativeEvent.data);
switch (data.type) {
case 'postTest':
console.log('受け取りました。');
break;
default:
break;
}
};
return (
<View style={styles.container}>
<WebView
style={styles.container}
containerStyle={styles.container}
source={{ uri: 'https://example.com/' }}
injectedJavaScript={injectedJavaScript}
onMessage={_onMessage}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
解説
WebViewコンポーネントのinjectedJavaScript
propsにはWebView内で実行したいJavaScriptを渡してあげます。今回はwindow.ReactNativeWebView.postMessage()
としてtype
プロパティにpostTest
という文字列を渡してあげます。
const injectedJavaScript = `
window.ReactNativeWebView.postMessage(
JSON.stringify({
type: 'postTest',
})
);
`;
このinjectedJavaScriptはWebViewコンポーネントが読み込まれた時に実行されます。もし、実行するタイミングをずらしたいのであれば、domcontentloaded
やmutationObserver
、setTimeout
、クリックイベントなら.addEventListener('click', function(event) {});
などを使うと良いでしょう。
そして、このpostMessageをアプリ側で受け取るためにonMessage={_onMessage}
として、定義しています。
const _onMessage = async (event: WebViewMessageEvent) => {
const data = JSON.parse(event.nativeEvent.data);
switch (data.type) {
case 'postTest':
console.log('受け取りました。');
break;
default:
break;
}
};
引数にeventを受け取ることができて、event.nativeEvent.data
でpostMessageしたデータを受け取ることができます。今回はtype: 'postTest',
なので、event.nativeEvent.data.type
で'postTest'を受け取ることができます。他にも
const injectedJavaScript = `
window.ReactNativeWebView.postMessage(
JSON.stringify({
type: 'postTest',
id: 1
})
);
`;
とすればevent.nativeEvent.data.id
としてデータを受け取ることができます。今回のようにswitch-case句で記述すると、他にもinjectedScriptでpostMessageした場合にもハンドリングすることができます。
最後にcase 'postTest'
の中で実行したい処理を記述します。コンポーネントをレンダリングしたり、stateを更新したりできます。
まとめ
いかがだったでしょうか。個人的にはブラウザでDOM操作していることになるので、ロード時に実行したいJavaScriptがある場合はローディングを挟んだ方がいいかなと思いました。あと、injectScript内で処理が失敗すると、後続の処理はもちろん実行されなくなるので、注意が必要です。
参考
Discussion