せっかくReact NativeなのにWebView使うの?って言われそうですが、過去のWeb資産をそのまま使えるので、Markdownエディタの表示機能で採用するなど適材適所で使っていくのはありだと思います。
しかし、オフィシャルのWebviewはUIWebviewを使っているので、ブラウザの下に出てくるナビゲーションバーを消せないなど常にフルスクリーンのWebviewを使いたいときなどは使うことができません。
そこで、今回説明するのがreact-native-wkwebviewというもライブラリです。それでは見ていきましょう。
Nativeライブラリを使うのでExpoユーザはejectする必要があります。要件を整理して必要な場合に使いましょう
Installation
READMEは、npm install
とrnpm
を使ってますが、今風に下記のようにします。
$ yarn add react-native-wkwebview-reborn
$ react-native link react-native-wkwebview-reborn
使い方
全てではありませんが、オフィシャルのWebviewと同じ名前のAPIが生えてます。ほぼほぼ同じように使えると思います。
自前のHTMLを読み込む
まずは、基本。変数にHTMLを書いて、sourceで読み込む。
const HTML = `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
...
`;
...
<WKWebView
source={{ html: HTML }}
自前のHTMLの中身を動的に変更する
html内にユニークな文字列を定義しておいて、それを置換する。
そして、それをstateに入れておくと、state変更時にviewも変わる。
const HTML = `
<!DOCTYPE html>
<html lang="ja">
<head>
<title>THIS_TITLE</title>
<meta charset="UTF-8">
...
`;
...
constructor(props) {
super(props);
this.state = {
html: EDITOR_HTML.replace(/THIS_TITLE/g, props.title)
};
}
...
<WKWebView
source={{ html: this.state.html }}
自前のHTMLの中身を動的に変更する(その2)
HTML内に仕掛けておいたJSの関数を走らせることでHTML/JSの状態を変更する。
refsからevaluateJavaScript
を使うことでJS関数を呼び出せる。
const HTML = `
<!DOCTYPE html>
<html lang="ja">
<head>
<title>THIS_TITLE</title>
...
<body>
<script>
window.receivedChangeFontSizeFromReactNative = function(fontSize) {
document.getElementById("xterm").style["font-size"] = fontSize + "px";
}
</script>
`;
...
componentWillReceiveProps(nextProps) {
if (this.props.fontSize !== nextProps.fontSize) {
this.refs.terminal.evaluateJavaScript(
`receivedChangeFontSizeFromReactNative(${nextProps.fontSize})`
);
}
}
...
render(){
return(
<WKWebView ref="terminal" />
)
}
evaluateJavaScriptでオブジェクトを渡す
evaluateJavaScript
はオブジェクトを渡せないので、JSONで文字列にシリアライズして使う。
window.receivedCursoFromReactNative = function(cursorStr) {
var cursor = JSON.parse(cursorStr);
editor.selection.moveTo(cursor.row, cursor.column)
}
...
let cursorStr = JSON.stringify({ row: 0, column: 0 });
this.refs.editor.evaluateJavaScript(
`receivedCursoFromReactNative('${cursorStr}')`
);
自前HTMLから呼び出すJSライブラリをローカルに保存しといて読み込む
オフラインで使う場合、使いたいライブラリをファイルから読み込めます。
RNFS
が別途必要なので使えるようにしておいく。
$ yearn add react-native-fs
$ react-native link react-native-fs
そして、使うライブラリをダウンロードしてきてXcodeにdrag&drop.
するとHTMLからアクセスできようになる。
注意点として、js/cssをいじった場合は、ビルドが必要になること。
const TERMINAL_HTML = `
<!DOCTYPE html>
<html lang="ja">
<head>
<link rel="stylesheet" href="xterm.css" />
<script src="xterm.js"></script>
...
`;
...
<WKWebView
source={{ html: this.state.html, baseUrl: RNFS.MainBundlePath }} />
JS内でのイベントをReact Nativeコンポーネントに渡す。
HTMLのJSイベントをRN側に渡せます。例えば、チャットのソケット通信をWebviewで実装しておいて、結果をRN側に渡すなどですね。RNに渡せば、Reduxなどで状態管理できますね。
const HTML = `
...
editor.getSession().on("change", function(e){
var content = editor.getValue();
window.webkit.messageHandlers.reactNative.postMessage({content: content});
});
...
_onMessage(message) {
if (message.body.content) {
...
}
}
...
<WKWebView
onMessage={this._onMessage.bind(this)} />
...
`;
インターネット上のURLを開き、JSをその都度走らせる。
ロードが終わるたびにjsが走る。自前HTMLのように変数にJSを書いて入れる。
注意点として、loadするたびに入るので明示的にreload()
してやる必要がある。
下記の例はwindowサイズが変わったときの挙動を変えています。
let zoomOutJs = `
var metaTag=document.createElement('meta');
metaTag.name = "viewport";
metaTag.id="viewport-meta";
metaTag.content = "user-scalable=no,width=BROWSER_WIDTH";
document.getElementsByTagName('head')[0].appendChild(metaTag);
`;
<WKWebView
source={{ uri: "https://www.google.com" }}
injectedJavaScript={zoomOutJS.replace("BROWSER_WIDTH", width)}
/>
ドキュメントされてないが生えてるメソッド
refで参照可能。詳しくはソース参照
- this.refs.terminal.reload()
- this.refs.terminal.goBack()
- this.refs.terminal.goForward()
<WKWebView refs="terminal">
target="blank"などのリンクを踏んだときにSafariで開かないで自身のwebviewで開く
<WKWebview
openNewWindowInWebView={true} />
(< > and Done)
のナビゲーションを表示しない
<WKWebview
hideKeyboardAccessoryView={true} />
## まとめ
DHHもつぶやいてますが、iPhoneのjetStream(JSベンチマーク)スコアは、andoroidを凌駕しています。
iphone8に限ればMBPに僅差とか。こういった事実もiPhoneでWebViewを選択する理由の一つになると思います。
JetStream too artificial for you? Checkout Speedometer 2 scores. Android is so far behind it's not even funny 😮 https://t.co/z3nOUWGrIG pic.twitter.com/FRdrkV2jy9
— DHH (@dhh) October 4, 2017