😸

React Native の WebView 内外でやりとりする

2020/12/21に公開

最近 React Native をやり始めた erukiti です。ビギナーです。優しくしてください。

諸々の事情から WebView で構成されたコンテンツを描画することになりました。

WebView は、ウェブフロントエンド的にわかりやすく言えば、IFrame のような技術です。WebView コンポーネントの中では、静的な HTML か、どこかのサイトのウェブページが内部にレンダリングされます。

Android Native なら同名の WebView があり、iOS/macOS なら、WkWebView があります。

今回は、React Native における WebView で、WebView の中から外、あるいはその逆にやりとりをするための方法についてまとめました。

React Native の WebView

React Native の WebView は以前のバージョンだと組み込みだったものが、今は独立したパッケージになってるようですね。

さて今回の記事では WebView で、内から外、外から内への通信を行う方法です。

ちょうど、ドキュメントにCommunicating between JS and Native というそのものズバリなものがあります。

  1. React Native WebView のプロパティで JS を指定する injectedJavaScript
  2. React Native WebView の ref から JS を指定する injectJavaScript
  3. WebView の中身からメッセージを送信する window.ReactNativeWebView.postMessage と React Native WebView のプロパティでメッセージを受信する onMessage

この 3 つを使ってやりとりをすることになります。

injectedJavaScript

パッケージ公式の https://github.com/react-native-webview/react-native-webview/blob/master/docs/Guide.md#the-injectedjavascript-prop はクラスコンポーネントにおける説明です。関数型コンポーネント(Hooks)で書くとこんな感じでしょうか。

const App: React.FC = () => {
  return (
    <WebView
      style={{ flex: 1 }}
      source={{
        url: 'http://localhost:3000',
      }}
      injectedJavaScript={`
        alert('Injected!');
        true; // 必須
      `}
    />
  )
}

injectedJavaScrpt は文字列を JavaScript として実行するプロパティです。

  • console.log はどこに出るか不明なのでたぶん使えない
  • 末尾の true がなぜか必要 note: this is required, or you'll sometimes get silent failures

実行タイミングによって、

という亜種もあります。

基本的には、ページを読み込む前にグローバルに影響を及ぼすコードを走らせるものです。

injectJavaScript

初期化時点でグローバルに JS を実行するだけだとあまりに不便です。そこで WebView の ref からメソッドで実行する injectJavaScript というものも用意されています。

const App: React.FC = () => {
  const ref = React.useRef<any>()
  React.useEffect(() => {
    setTimeout(() => {
      ref.injectJavaScript(`
        alert('inject!');
        true;
      `)
    }, 3000)
  }, [])
  return (
    <WebView
      ref={ref}
      style={{ flex: 1 }}
      source={{
        url: 'http://localhost:3000',
      }}
    />
  )
}

型は適当。

ref.injectJavaScript(code) で任意のタイミングでコードを実行可能です。Button を押したらコードを実行する、生体認証が OK ならコードを実行する、のような処理が可能です。

postMessage / onMessage

WebView 内部で実行されてるコードの方から、React Native のアプリへ働きかけたいときに使える方法も用意されています。

window.ReactNativeWebView.postMessage(message) です。ただし、message は文字列でなければいけません。複数のデータ、複雑なデータ構造などを送りたい場合は JSON なりにすると良いでしょう。

postMessage で送信された文字列は onMessage プロパティで受け取ることができます。

// React Native 側
const App: React.FC = () => {
  return (
    <WebView
      style={{ flex: 1 }}
      source={{
        url: 'http://localhost:3000'
      }}
      onMessage={(ev) => console.log(ev.nativeEvent.data)}
    >
  )
}
// WebView 内部のコード
if ('ReactNativeWebView' in window) {
  window.ReactNativeWebView.postMessage(
    JSON.stringify({ type: 'hoge', hoge: 'ほげ' }),
  )
}

のように使います。

postMessage なのに、送信だけしかできず返信を受け取ることが出来ないなど、使い勝手が良いとは言えませんが、一応 WebView 内部から通信が可能です。

まとめ

  • 初期化につかえる injectedJavaScript プロパティ(同系統のものが残り三つある)
  • ワンショットできる injectJavaScript メソッド
  • WebView 内部から送信できる postMessage 関数

統一感が無いですが、これらを組み合わせて使うしかないようです。

React Native 初心者なので、間違っているかもしれません。間違いや、こうしたほうがいい、みたいなものがあればぜひコメントなどを書いていただけると助かります。

Discussion