React NativeとWebViewでいつでも双方向通信する
create: 9/3/2020
React React Native

最近仕事で React を使うようになりました。
で、React Native アプリ(以下 RN)と WebView(以下 WV)の双方向でメッセージをやり取りしたかった。

公式ドキュメントの奥深く、Communicating between JS and Nativeに書いてある。

というわけで、いつでも双方向通信を実現してみた。

動作確認

  • react-native: 0.63.2
  • react-native-webview: 10.8.3

ReactNative 側のソース

react-nativeWebViewはディスコンらしい。
ので react-native-webviewWebViewを使います。

sourceプロパティで Web ページを埋め込むのですが、localhostじゃ動きません。
アドレスを指定しましょう。動作検証するためにはこのアドレスが RN 側の端末で解決できる必要があります。

import React, { useState } from 'react'
import { StyleSheet, View, Text, Button } from 'react-native'
import { WebView } from 'react-native-webview'

function App(props) {
  const [msg, setMessage] = useState('NO HANDLE MESSAGE')
  const [webref, setWebref] = useState()

  // WebViewからのメッセージを受信
  function onMessage(message) {
    setMessage(message.nativeEvent.data)
  }

  // WebViewにメッセージを送信
  let count = 1
  function postMessage() {
    webref.postMessage(`HELLO JS!! ${count++}`)
  }

  return (
    <View style={{ flex: 1 }}>
      <WebView
        ref={setWebref}
        source={{ uri: 'http://192.168.11.15:3000/' }} // localhostじゃ動かないので
        onMessage={onMessage}
      />
      <Text style={styles.floatText}>{msg}</Text>
      <Button onPress={postMessage} title="POST MESSAGE" />
    </View>
  )
}
const styles = StyleSheet.create({
  floatText: {
    top: -300,
    color: 'white',
  },
})

WebView からメッセージを受け取る

WV からメッセージを受け取るため、WebViewonMessaageプロパティにハンドラonMessage関数を仕掛けます。

WebView にメッセージを送信する

WebViewコンポーネントのpostMessageメソッドを通じて送信します。
WebViewへの参照が必要なので、ref プロパティでwebrefState に参照を保持します。
ボタンを押したらpostMessage関数で WV 側にメッセージを送信します。

WebView 側のソース

import React, { useState } from 'react'
import './App.css'

function App() {
  const [msg, setMessage] = useState('INIT.')

  // RNにメッセージを送る
  let count = 1
  function postMessage(msg) {
    if (window.ReactNativeWebView) {
      // 存在チェック
      window.ReactNativeWebView.postMessage(`${msg} ${count++}`)
    }
  }

  // RNからメッセージを受け取る
  if (window.ReactNativeWebView) {
    // 注意!! messageが送られるのはwindowではなくdocument
    document.addEventListener('message', (msg) => {
      setMessage(msg?.data)
    })
  }

  return (
    <div className="App">
      <header className="App-header">
        <div>{msg}</div>
        <button onClick={() => postMessage('HELLO? NATIVE!!')}>
          POST MESSAGE
        </button>
      </header>
    </div>
  )
}

export default App

ReactNative からメッセージを受け取る

windowmessageイベントで受け取るのかと思いきやdocumentmessageイベントでした。
これで 1 時間くらいハマった。

ReactNative にメッセージを送信する

以前はwindow.postMessageだったらしいですが、現在はwindow.ReactNativeWebView.postMessageを使えとのこと。
RN を通さずにローカルでサイトを表示するとwindow.ReactNativeWebViewが無くて落ちるので、存在チェックを入れます。

できた!

WV 側のリスナーがwindowじゃなくてdocumentだったのにハマりましたが、無事両方にメッセージを送信することができました!
文字列の送信のみなので、オブジェクトの送受信をしたければJSON.stringifyJSON.parseを駆使することになると思います。

Reactが書ければネイティブアプリが作れるなんて。Reactすげぇな!
(vue も好きだけど)これだけでVue.jsから乗り換える価値あり。

参考サイト

Communicating between JS and Native
https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#controlling-navigation-state-changes