↔️

[ReactNative] WebViewとアプリでデータをやりとりする

2024/11/15に公開

前提

WebViewで表示している画面の操作をアプリでハンドリングしたい

ネイティブアプリケーションを開発していると、どうしてもWebViewを使用せざるを得ないことって多くないでしょうか?例えば、ECのアプリで決済周りの部分がAPI開発すると色々面倒なので、WebViewでそのまま表示させるとか、スクラッチで作ると工数がかかるから一部分はWebViewにする...などなど。
そんな時に、「WebView内でのイベントをキャッチしてアプリ側でハンドリングしたい」なんてことがしばしばあるかと思います。(モーダル出すとか、アプリの画面に遷移させるとか)
今回はそんな時に、どのようにしてやりとりを行うか説明していきます。

流れ

  1. react-native-webview<WebView />コンポーネントのinjectedJavaScript propsでWebView内で実行したいJavaScriptを記述。
  2. そのJavaScriptの中でwindow.ReactNativeWebView.postMessage()する。
  3. アプリ側ではonMessage={} propsでWebView内からのpostMessageを受け取る。
  4. 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コンポーネントが読み込まれた時に実行されます。もし、実行するタイミングをずらしたいのであれば、domcontentloadedmutationObserversetTimeout、クリックイベントなら.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内で処理が失敗すると、後続の処理はもちろん実行されなくなるので、注意が必要です。

参考

https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md

https://zenn.dev/erukiti/articles/react-native-webview-comm

GitHubで編集を提案

Discussion