
この記事は「Reactは少しわかるけど、React Nativeは初めて」という人向けです
React Nativeは、Webサイトを作るための「React」と考え方がほぼ同じです。そのため、Reactを知っている人は、React Nativeをすぐに使い始められます。
ただ、実際に書き始めると、「div が使えない」「CSSが効かない」「並び方が逆になった」のように、Webの感覚のままだと戸惑うところがいくつもあります。
この記事では、
- ReactとReact Nativeで 同じところ
- ReactとReact Nativeで 違うところ(部品、スタイル、レイアウト、イベント、画面遷移、保存、公開など)
- Webの知識で書いたときに つまずきやすいポイント
を、コードを比べながら説明します。React Nativeをゼロから学びたい人は、先に入門シリーズの第1回から読むのがおすすめです。
ひとことで言うと
考え方は同じ。画面に出す部品と、見た目の付け方と、動く場所が違う。
料理にたとえると、ReactもReact Nativeも「同じレシピ(考え方)」を使います。でも、Reactは「ガスコンロ(ブラウザ)」で、React Nativeは「IHコンロ(スマホ)」で料理をします。レシピは同じでも、使える鍋(部品)や火加減の調整方法(スタイル)が少し違う、というイメージです。
同じところ
まずは、そのまま使える知識からです。
- コンポーネントは「画面の中身を返す関数」
- propsで部品に情報を渡す
useState、useEffect、useMemoなどのフック- 配列を
mapで一覧にして、keyを付ける - JSXで書く
- TypeScriptが使える
たとえば、ボタンを押すと数字が増えるカウンターを比べてみましょう。
React(Web)
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
React
import { useState } from "react";
import { Pressable, Text, View } from "react-native";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text>{count}</Text>
<Pressable onPress={() => setCount(count + 1)}>
<Text>+1</Text>
</Pressable>
</View>
);
}
useState の使い方や、count を表示する書き方はまったく同じです。違うのは、div が View に、p が Text に、button が Pressable に、onClick が onPress に変わっているところだけです。
ここからは、この「違うところ」を1つずつ見ていきます。
違い1: 部品(HTMLのタグは使えない)
React Nativeでは、div や p のような HTMLのタグは使えません。書くとエラーになります。代わりに、React Nativeが用意している部品(コンポーネント)を使います。
React(Web) | React Native | 役割 |
|---|---|---|
|
| 部品をまとめる箱 |
|
| 文字 |
|
| 画像 |
|
| 押せる場所 |
|
| 文字の入力 |
|
| 一覧 |
(ページのスクロール) |
| スクロールできる場所 |
|
| 別の画面へのリンク |
文字は必ず Text の中に書く
Webでは、<div>こんにちは</div> のように、どこにでも文字を書けました。React Nativeでは、文字は必ず Text の中に書く 必要があります。
// ✕ エラーになる
<View>こんにちは</View>
// ○ 正しい
<View>
<Text>こんにちは</Text>
</View>
見出しの h1 や h2 はない
h1 のような「見出し用のタグ」もありません。見出しは、Text に大きな fontSize と太い fontWeight を付けて作ります。
画像は大きさを指定する
Webの img は、大きさを書かなくても、画像の元の大きさで表示されます。React Nativeの Image でインターネット上の画像を表示するときは、width と height を指定しないと表示されません。
<Image source={{ uri: "https://example.com/photo.png" }} style={{ width: 120, height: 120 }} />
違い2: スタイル(CSSファイルは使えない)
classNameではなく、JavaScriptのオブジェクトで書く
Webでは、CSSファイルにスタイルを書いて、className で付けるのが基本でした。React Nativeでは、CSSファイルは使えません。スタイルはJavaScriptのオブジェクトとして書き、style で付けます。
Web(CSS)
.title {
font-size: 24px;
font-weight: bold;
background-color: #e0f2fe;
}
React
const styles = StyleSheet.create({
title: {
fontSize: 24,
fontWeight: "bold",
backgroundColor: "#e0f2fe",
},
});
<Text style={styles.title}>タイトル</Text>
書き方のルールは3つです。
font-sizeではなくfontSizeのように、ハイフンをなくして、2つ目の単語の頭を大文字にする- 数字に 単位(
pxなど)を付けない。24と書けば、端末に合った大きさになる - 値が文字のときは
"bold"のように" "で囲む
スタイルは親から子に引き継がれない
ここはWebの感覚だと一番つまずきやすいところです。
Webでは、body に color: red を書くと、中の文字が全部赤くなりました。スタイルが親から子に 引き継がれる(継承される) からです。
React Nativeでは、View に付けた文字のスタイルは、中の Text に引き継がれません。
// ✕ 文字は赤くならない(Viewには文字の色を付けられない)
<View style={{ color: "red" }}>
<Text>こんにちは</Text>
</View>
// ○ Textに直接付ける
<View>
<Text style={{ color: "red" }}>こんにちは</Text>
</View>
例外として、Text の中の Text には、スタイルが引き継がれます。文章の一部だけを太字にしたいときに便利です。
<Text style={{ color: "red" }}>
これは<Text style={{ fontWeight: "bold" }}>大事</Text>なことです
</Text>
この例では、「大事」の部分も赤くなり、さらに太字になります。
:hover やメディアクエリはない
CSSの :hover(マウスを乗せたとき)や、@media(画面の幅で見た目を変える)も使えません。スマホにはマウスがないので、:hover の代わりに「押している間」の見た目を変えます。
<Pressable style={({ pressed }) => [styles.button, pressed && { opacity: 0.6 }]}>
<Text>押してね</Text>
</Pressable>
Pressable の style に関数を渡すと、pressed(押されているかどうか)を受け取れます。
画面の幅によって見た目を変えたいときは、useWindowDimensions で画面の大きさを取得して、自分で切りかえます。
import { useWindowDimensions } from "react-native";
const { width } = useWindowDimensions();
const columns = width > 600 ? 3 : 2;
iPhoneとAndroidで見た目を変えたいとき
React Nativeならではの機能として、Platform を使うと、iPhoneとAndroidでスタイルを変えられます。
import { Platform } from "react-native";
const styles = StyleSheet.create({
card: {
padding: 16,
...Platform.select({
ios: { shadowOpacity: 0.2, shadowRadius: 8 },
android: { elevation: 4 },
}),
},
});
影の付け方は、iPhone(shadow〇〇)とAndroid(elevation)で書き方が違う代表例です。
違い3: レイアウト(Flexboxが基本で、縦並びが初期値)
何も書かなくてもFlexbox
Webでは、Flexboxを使いたいときに display: flex を書きました。React Nativeでは、すべての View が最初からFlexbox です。display: flex を書く必要はありません。CSSの grid は使えないので、並べ方はFlexboxで考えます。
初期値は縦並び
Webの display: flex は、何も書かないと 横に 並びます(flex-direction: row)。React Nativeは、何も書かないと 縦に 並びます(flexDirection: "column")。
// 横に並べたいときは、はっきり書く
<View style={{ flexDirection: "row" }}>
<Text>左</Text>
<Text>右</Text>
</View>
そのほかの細かい違い
Webのつもりで書くと、少しずつ動きが違うところがあります。
項目 | Web(CSS) | React Native |
|---|---|---|
|
|
|
|
|
|
|
| 数字1つだけ( |
|
|
|
特に flexShrink は、「横に並べた文字が画面からはみ出す」原因になりがちです。長い文字を横に並べるときは、文字の側に flex: 1 や flexShrink: 1 を付けると、画面の中で折り返してくれます。
違い4: イベント(onClickではなくonPress)
やりたいこと | React(Web) | React Native |
|---|---|---|
押したとき |
|
|
長押ししたとき | (標準ではない) |
|
文字が変わったとき |
|
|
文字の入力では、受け取り方も違います。
React(Web)
<input value={name} onChange={(event) => setName(event.target.value)} />
React
<TextInput value={name} onChangeText={setName} />
Webの onChange では、event.target.value から入力された文字を取り出しました。React Nativeの onChangeText には、入力された文字がそのまま届く ので、setName をそのまま渡せます。
また、TextInput には、スマホならではの設定がたくさんあります。
<TextInput
keyboardType="number-pad" // 数字のキーボードを出す
secureTextEntry // パスワードのように ●●● で隠す
autoCapitalize="none" // 最初の文字を自動で大文字にしない
/>
違い5: スクロールと一覧
Webでは、中身が画面より長くなると、ページ全体が自動でスクロール しました。React Nativeでは、自動ではスクロールしません。スクロールさせたい場所を、ScrollView か FlatList で囲む必要があります。
ScrollView: 中身を一度に全部作る。量が少ないとき向けFlatList: 画面に見えている部分だけを作る。一覧のデータが多いとき向け
Webで ul と li を map で並べていたような一覧は、React Nativeでは基本的に FlatList を使います。
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.title}</Text>}
/>
違い6: 画面遷移(URLではなく、画面を重ねる)
考え方の違い
Webの画面遷移は、URLを切りかえる 仕組みです。ブラウザの「戻る」ボタンを押すと、1つ前のURLに戻ります。
スマホアプリの画面遷移は、画面をトランプのように上に重ねていく 仕組み(スタック)が基本です。新しい画面は今の画面の上に重なり、「戻る」を押したり、画面のはしから横にスワイプしたりすると、一番上の画面が取りのぞかれます。画面の下にボタンが並ぶ タブ もよく使われます。
Expo RouterはNext.jsのApp Routerに近い
Expoでは、Expo Router で画面遷移を作ります。Expo Routerは「app フォルダにファイルを作ると、それが画面になる」仕組みなので、Next.jsのApp Routerを知っている人には、なじみやすい書き方です。
Next.js(App Router) | Expo Router | |
|---|---|---|
画面 |
|
|
動的な画面 |
|
|
外わく |
|
|
リンク |
|
|
値の受け取り |
|
|
戻ってきたときに、画面は作り直されない
ここもWebの感覚だとつまずきやすいところです。
スタックでは、新しい画面に移動しても、下の画面は消えずに残っています。そのため、詳しい画面から一覧の画面に戻ってきても、一覧の画面は作り直されず、useEffect(() => { ... }, []) も もう一度は動きません。
「戻ってきたら一覧を読みこみ直したい」ときは、画面が表示されるたびに動く useFocusEffect を使います。
import { useFocusEffect } from "expo-router";
import { useCallback } from "react";
useFocusEffect(
useCallback(() => {
loadItems(); // 画面が表示されるたびに読みこみ直す
}, []),
);
違い7: 保存とスマホの機能
localStorageではなくAsyncStorage
Webでは、ちょっとしたデータの保存に localStorage を使いました。React Nativeには localStorage がないので、AsyncStorage を使います。
React(Web)
localStorage.setItem("name", "たろう");
const name = localStorage.getItem("name");
React
import AsyncStorage from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem("name", "たろう");
const name = await AsyncStorage.getItem("name");
名前のとおり、AsyncStorageは 非同期(Async) なので、await で終わるのを待つ必要があります。また、パスワードやログインの情報のような、人に見られたくないデータは、AsyncStorageではなく、暗号化して保存してくれる expo-secure-store を使います。
スマホの機能が使える
Webではほとんど使えなかった、スマホの機能を使えるのは、React Nativeの大きな強みです。
- カメラ、写真の選択(
expo-camera、expo-image-picker) - 通知(
expo-notifications) - 位置情報(
expo-location) - 振動(
expo-haptics)
こうした機能を使うときは、多くの場合、ユーザーに許可をもらう 必要があります。「カメラへのアクセスを許可しますか?」というあの画面です。
違い8: 使えるライブラリ
npmのライブラリの多くは、React Nativeでも使えます。ただし、ブラウザの仕組み(window や document、HTMLのタグ)に頼っているライブラリは動きません。
ライブラリの種類 | React Nativeで使えるか |
|---|---|
計算やデータを扱うもの(日付の計算、状態管理のZustandなど) | 使えることが多い |
画面の部品のライブラリ(Webの部品集など) | 使えない。React Native用のものを探す |
CSSのライブラリ | 使えない。Tailwindの書き方をしたいなら、NativeWindなどのReact Native用のものを使う |
ライブラリを入れるときは、npm install ではなく npx expo install を使うと、今のExpoのバージョンに合うものを選んでくれます。
違い9: 動作確認とデバッグ
React(Web) | React Native(Expo) | |
|---|---|---|
動作確認 | ブラウザで開く | Expo Goアプリ、シミュレーター、実機 |
| ブラウザの開発者ツール | ターミナル |
開発者ツール | ブラウザの開発者ツール | React Native DevTools( |
要素の確認 | 開発者ツールで、HTMLやCSSを直接見られる | DevToolsで、コンポーネントの中身を見られる |
Webのように「開発者ツールでCSSを直接いじって試す」ことはできないので、スタイルはコードを書きかえて、Fast Refresh(保存するとすぐに反映される仕組み)で確認しながら調整します。
違い10: 公開と更新
Webサイトは、サーバーに置けばすぐにみんなが見られます。直したいときも、サーバーのファイルを入れかえれば、次に開いた人から新しい内容になります。
スマホアプリは、App StoreやGoogle Playの審査 を通らないと、みんなに使ってもらえません。審査には、数時間から数日かかることがあります。そして、ユーザーがアプリを更新してくれるまでは、古いバージョンが使われ続けます。
そのため、スマホアプリでは、
- 公開する前のテスト をWebよりしっかりする(iPhoneならTestFlightで、配る前に実機で試せる)
- 古いバージョンのアプリが残ることを考えて作る(サーバー側の仕組みを急に変えない)
といったことが大切になります。
なお、Expoには EAS Update という仕組みがあり、JavaScriptのコードの修正なら、ストアの審査を通さずにアプリへ届けられます。ただし、新しいスマホの機能を追加するときのように、アプリ本体(ネイティブの部分)を変えるときは、ストアでの更新が必要です。
Webとスマホで、コードを共有できる?
ExpoとReact Nativeは、Webで動かすこともできます(npx expo start の画面で w キーを押すと、ブラウザで開けます)。ただ、最初から「1つのコードで、Webもスマホも完璧に」を目指すと、細かい違いに苦労しがちです。
まずは、画面の部品はそれぞれで作り、ロジック(計算、データの取得、状態管理など)を共有する と考えるのが現実的です。フックや関数は、View や div を使わない限り、ReactでもReact Nativeでも同じように動きます。
まとめ
項目 | React(Web) | React Native |
|---|---|---|
考え方(コンポーネント、props、state、フック) | 同じ | 同じ |
部品 |
|
|
文字 | どこにでも書ける |
|
スタイル | CSS、 |
|
スタイルの継承 | 親から子に引き継がれる | 引き継がれない( |
並べ方 |
| 最初からFlexboxで縦並び |
イベント |
|
|
スクロール | ページが自動でスクロール |
|
画面遷移 | URLを切りかえる | 画面を重ねる(スタック)、タブ |
保存 |
|
|
スマホの機能 | ほとんど使えない | カメラ、通知、位置情報などが使える |
公開 | サーバーに置けばすぐ | ストアの審査が必要 |
Reactを知っている人は、「考え方はそのまま、部品とスタイルを覚え直す」つもりで始めると、スムーズにReact Nativeを使えるようになります。
React Nativeをゼロから、手を動かしながら学びたい人は、入門シリーズも読んでみてください。


