WebViewからReact Nativeの関数を型安全に呼び出す
今までCapacitor(=WebViewのみ)で実装されていたユビーのモバイルアプリ(Android/iOS)を、React NativeとWebViewを組み合わせたハイブリッドアプリとして刷新しています。その過程で、WebView内で実行されるJavaScriptから、React Native側で定義した関数を型安全に呼び出せるライブラリを実装しました。その使用例と仕組みを紹介します。
使い方
React Native側の実装
まずライブラリをインストールします。Peer dependenciesであるreact-native-webviewとcomlinkも入れてください。
npm install @react-native-webview-rpc/native
npm install react-native-webview comlink # peer dependencies
次に、WebViewから実行したい関数を実装します。ここではネイティブのアラートを出す関数を実装します。
加えて、関数の型をエクスポートしておきます。これは後述するWeb側の実装で参照します。
import { Alert } from "react-native";
const rpcs = {
async alert(title: string, body: string) {
Alert.alert(title, body);
return "ok";
},
};
export type WebViewRpcs = typeof rpcs;
最後に、useWebViewRpcHandler
を用いてメッセージイベントのハンドラを取得します。これをreact-native-webviewが提供するWebViewコンポーネントのプロパティに渡してください。
import { useRef } from "react";
import WebView from "react-native-webview";
import { useWebViewRpcHandler } from "@react-native-webview-rpc/native";
import { rpcs } from "./rpcs";
export default function App() {
const ref = useRef<WebView>(null);
const onMessage = useWebViewRpcHandler(ref, rpcs);
return (
<WebView
ref={ref}
onMessage={onMessage}
source={{ uri: "http://localhost:5173" }}
/>
);
}
Web側の実装
まずは同じくライブラリをインストールします。@react-native-webview-rpc/nativeではなく@react-native-webview-rpc/webであることと、react-native-webviewが不要であることに注意してください。
npm install @react-native-webview-rpc/web
npm install comlink # peer dependencies
次にwrap
を用いて、React Nativeの関数を実行するためのプロキシオブジェクトを生成します。型引数には、先述のrpcs.tsx
からエクスポートしたWebViewRpcsを渡します。
import { wrap } from '@react-native-webview-rpc/web';
import type { WebViewRpcs } from "../native/rpcs";
const rpcs = wrap<WebViewRpcs>();
このrpcsオブジェクトを通して、React Native側で定義した通りのシグネチャで関数を実行することができます。
const result = await rpcs.alert("Hello", "World");
仕組み
Peer dependenciesでお察しかもしれませんが、Comlinkを使って実装しています。Comlinkは元々はWeb Worker内のオブジェクトや関数を透過的に扱えるようにするライブラリですが、Web Worker相当のインターフェースがEndpointという概念に抽象化されていて、postMessageでメッセージを送ってonmessageイベントでメッセージを受け取るようなメッセージングの仕組みがあれば応用することができます。
例えば、iframeのようにwindowオブジェクトを持つものは window.postMessage()
と window.addEventListener()
でメッセージングができます。それに対応するEndpointがビルトインで提供されています。
では、WebViewをこのインターフェースで扱うにはどうしたらよいでしょうか。
WebView -> React Native のメッセージ
まずは関数呼び出しを伝えるメッセージです。
react-native-webviewはWebView内のwindowオブジェクトにpostMessageを生やしてくれます。Web側でこれを叩いて WebView から React Native にメッセージを送ります。
このメッセージを React Native 側のonMessageプロパティで受け取り、Comlinkが登録するリスナーに流します。メッセージの中身を解釈して関数を実行するのはComlinkがやってくれます。
React Native -> WebView のメッセージ
次に関数呼び出しのレスポンスを伝えるメッセージです。
React Native からは injectJavaScript を用いて、WebView内でメッセージイベントを発火させます。JSONはJavaScriptのサブセットですから、JSON.stringify
したメッセージをinjectするといい具合にオブジェクトとして解釈されます。
Web側ではこのイベントに対して素直にリスナーを登録します。メッセージの解釈は同様にComlinkがやってくれます。
このように、React Native, Webの両方でEndpointインターフェースに合わせてメッセージングの実装をするだけで、残りはComlinkの仕組みを使いまわせます。Proxyを駆使した透過的呼び出しやメッセージのシリアライズなどについて考えなくて済むのでとっても楽です。
おわりに
Comlinkのイケてる抽象化によって、WebViewからReact Nativeの関数を型安全に呼び出せた事例を紹介しました。ユビーではWeb技術を駆使したモバイルアプリの開発も加速しています。少しでも興味持ってくださった方はぜひお話しましょう!DMでもpittaでも何でもご連絡ください。
Discussion