2020-09-02

React NativeとWebViewでいつでも双方向通信する

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) {  
    window.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イベントで受け取ります。

※ 初出時、 document.addEventListner で動作確認しましたが、今日確認したらwindow.addEventListenerでした。なぜ・・・?

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


猫派 / 基本インドア / ガジェット大好き / RDP推進派
コロナ禍の趣味はPC+VRでゲーム。
最近のゲーム:Factorio / BeatSaber / にゃんこ大戦争

→ Policy