はじめに
React Nativeでアプリを作ったあと、「同じ画面をWeb版としても出したい」と思ったことはないでしょうか。ネイティブアプリとWebアプリではViewとdiv、Textとspanのようにタグの体系がまったく違うため、素直にやるとロジックはそのままにUI部分だけを丸ごと書き直す羽目になります。
React-Native-For-Web(パッケージ名はreact-native-web)は、このViewやText、StyleSheet、PressableといったReact NativeのコンポーネントとAPIを、React DOM上でそのまま動かせるようにしたライブラリです。ネイティブではViewがUIViewに、Webではdivに変換されるだけで、書くコードは1つで済みます。ExpoやNativeBaseなど多くのマルチプラットフォームツールの土台としても採用されています。
とはいえ、読むより触った方が早いと思います。View・Text・Pressableを組み合わせたカウンターを用意したので、先に挙動を見たい方はこちらからどうぞ。
React-Native-For-Webとは
React-Native-For-Webは、React Nativeのコンポーネント・APIをWeb向けに実装し直したライブラリです。necolas氏によって開発され、TwitterのモバイルWeb版(当時)を支えた実績を持ちます。React Native本体のAPIサーフェスに追従しながら、出力先はReact DOMのDOM要素になります。
主な特徴
- 段階的な採用が可能 - 既存のReact DOMプロジェクトに一部だけ組み込むこともでき、全面移行しなくても使い始められる
- アクセシビリティ対応 -
ViewやTextが意味のあるHTML要素・ARIA属性を出力するため、素のdiv実装より配慮された出力になる - タッチ・マウス・キーボードの統合 -
Pressableが指・マウス・キーボードいずれの操作にも同じイベントハンドラで応答する - スコープ付きスタイリング -
StyleSheet.createによるスタイル定義がクラスの衝突を避けつつ、自動でベンダープレフィックスも付与する
インストール
npmまたはyarnでreact-native-webをインストールします。
npm install react-native-web
Webpackなどのバンドラーでネイティブと共通のソースを使う場合は、react-nativeをreact-native-webにエイリアスする設定を加えます。
// webpack.config.js
resolve: {
alias: {
'react-native$': 'react-native-web',
},
},
エイリアスを使わずreact-native-webから直接importする書き方もそのまま利用できます。
React-Native-For-Webのサンプルを動かす
以下は、React-Native-For-WebのView・Text・Pressable・StyleSheetを組み合わせたカウンターです。Pressableはタッチ・マウス・キーボードのどの操作でも同じonPressハンドラで反応し、StyleSheet.createで定義したスタイルが自動的にCSSへ変換されます。
import { View, Text, Pressable, StyleSheet } from 'react-native-web';
function Counter() {
const [count, setCount] = useState(0);
return (
<View style={styles.box}>
<Text style={styles.label}>{count}</Text>
<Pressable style={styles.button} onPress={() => setCount(count + 1)}>
<Text style={styles.buttonText}>+1</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
box: { alignItems: 'center', gap: 12 },
label: { fontSize: 32, fontWeight: 'bold' },
button: { backgroundColor: '#2563eb', padding: 12, borderRadius: 8 },
buttonText: { color: '#fff', fontWeight: '600' },
});
実際に動かせるものが下です。ViewはDOM上ではdivに、Textはdiv(またはspan)に変換されますが、書いているコードはReact Native向けのAPIそのものです。
Pressableのstylepropに関数を渡すと、pressed状態を受け取って押下中だけ色を変えられます。このpressedをhoveredに見立てるような判定はできませんが、onPressをonLongPressに差し替えれば長押しでカウントが増える挙動に変わります。この先の「基本的な使い方」「実践的なユースケース」では、TextInputやAnimatedなど他のAPIの使い方をパターン別に見ていきます。
基本的な使い方
React-Native-For-Webのレイアウトは、CSSのFlexboxをそのままprops化したstyleオブジェクトで組み立てます。デフォルトのflexDirectionがcolumnである点はReact Nativeと同じで、Web標準のCSSとは異なるので注意が必要です。
import { View, Text, StyleSheet } from 'react-native-web';
function Card() {
return (
<View style={styles.card}>
<Text style={styles.title}>プロフィール</Text>
<Text style={styles.body}>React-Native-For-Webでは、
Viewがdivに、Textがテキスト要素に変換されます。</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
flexDirection: 'column',
padding: 16,
borderRadius: 12,
backgroundColor: '#f3f4f6',
},
title: { fontSize: 18, fontWeight: 'bold', marginBottom: 8 },
body: { fontSize: 14, color: '#374151' },
});
StyleSheet.createは単なるオブジェクトのラッパーですが、内部でスタイルをIDに変換してCSSの重複生成を防ぐ役割を持っています。ネイティブとWebで同じstylesオブジェクトを共有できるのが、React-Native-For-Webの中心的な価値です。
実践的なユースケース
TextInputによるフォーム入力
Webの<input>に相当するのがTextInputです。valueとonChangeTextで制御コンポーネントとして扱う点はReact Nativeと同じで、Web版のonChangeとは異なり文字列を直接受け取れます。
名前欄に文字を打つたびにonChangeTextが発火し、下のプレビュー文言がその場で切り替わります。placeholderやonChangeTextの中身はReact Nativeアプリと共通にできるため、バリデーションロジックだけをViewの外に切り出せば、モバイル版とWeb版でロジックを再利用できます。
Animatedによるアニメーション
Animated APIはAnimated.Valueという数値を時間経過で変化させ、それをtransformやopacityに橋渡しする仕組みです。React-Native-For-Web上でもAnimated.timingがそのままCSSトランジションとして再生されます。
「隠す」を押すとopacityが1から0へ400ミリ秒かけてAnimated.timingで遷移します。durationを短くすれば動きが速くなり、Easingをimportしてeasingプロパティに渡せば、加減速のカーブも自由に変えられます。
まとめ
React-Native-For-Webは、React NativeのView・Text・StyleSheet・Pressable・AnimatedといったコンポーネントとAPIを、React DOM上でそのまま動かせるライブラリです。ネイティブとWebでロジックとスタイル定義を共有できるため、モバイルアプリのコードベースをWebにも展開したいときに有力な選択肢になります。
まずは既存のReact Nativeコンポーネントを1つ選び、Webpackのエイリアス設定だけでブラウザに表示できるかを試してみてください。