注意点・落とし穴
Web 由来の感覚で書くと驚くポイントと、本番運用の前に知っておくべき RN 固有の癖をまとめる。 「Web の常識が通じない」「iOS と Android が違う」「実機でしか分からない」の3つが大きな軸。
Web との違い(よくある驚き)
flexDirectionのデフォルトは"column"。Web と逆。- テキストは
<Text>必須。<View>直下に文字列を書くとクラッシュ。 - CSS は使えない。
StyleSheet.create+ camelCase。 - cascade(継承)はほぼ無い。
color: inherit概念がない。例外は<Text>内の子<Text>。 - 単位はすべて DP(数値そのまま)。
px/emなし。 - Image はサイズ必須。Web のように勝手に決まらない。
- z-index は同じ親内のみ。Web のように離れた要素を被せる挙動が弱い。
- overflow: visible が iOS で効かないケースがある(影が切れる等)。
iOS / Android の違い
UI 慣習
- iOS: 戻るは画面左にスワイプ、Bottom Tab、SafeArea/ノッチ/Dynamic Island
- Android: 戻るボタン(システム)、Material Design、Status Bar の色制御
- 両方を「同じ見た目」に揃えるのではなく、各 OS のガイドラインを尊重するのが無難
影
iOS は shadow*、Android は elevation。両方書く必要あり。
キーボード
- キーボードの上昇挙動は OS で違う。
react-native-keyboard-controller推奨。 - OTP 自動入力の API が違う(iOS は
textContentType="oneTimeCode"、Android はautoComplete="sms-otp")。
パーミッション
- iOS は初回のみ ダイアログ。拒否されると設定アプリでしか復帰できない。
- Android は権限ごとにダイアログ、複数権限を一度に求めるか順に求めるかで挙動が違う。
- iOS のパーミッション文言(NSCameraUsageDescription 等)を
app.jsonに必ず書く。書かないと審査落ち。
フォント
- iOS のシステムフォントは San Francisco、Android は Roboto。
fontFamily指定なしだと別の見た目になる。 - カスタムフォントは
fontWeightが効かない(フォントファイル単位で別 family にする)。
ネットワーク
- Android は HTTPS が前提(API Level 28+)。HTTP を使うなら
android.usesCleartextTrafficを有効化。 - iOS のApp Transport Security (ATS) もデフォルトで HTTPS を強制。
状態とライフサイクルの違い
アプリの状態
AppState でアクティブ/バックグラウンドが取れる。バックグラウンドではタイマーが止まる等の挙動を意識。
import { AppState } from "react-native"
useEffect(() => {
const sub = AppState.addEventListener("change", (state) => {
// "active" / "background" / "inactive"
})
return () => sub.remove()
}, [])
画面のフォーカス
Stack ナビでは裏に隠れた画面もアンマウントされない。「画面に戻ってきた時にデータを再取得」等の挙動は useFocusEffect で:
import { useFocusEffect } from "expo-router"
useFocusEffect(
useCallback(() => {
refetch()
return () => { /* 画面を離れた時 */ }
}, [refetch]),
)
デバッグの落とし穴
Metro / キャッシュ
変な挙動の8割はキャッシュで治る:
npx expo start --clear
rm -rf node_modules && npm install
npx expo prebuild --clean # ネイティブをまっさらにする時
「ビルドは通るのに実機で動かない」
- ネイティブモジュールを足したのに Dev Build を作り直していない
- 権限文言が
app.jsonに無くて落ちている(iOS) - Hermes に未対応の文法を使っている
- Reanimated の Babel plugin を入れ忘れ
本番でしか起きるバグ
- 本番ビルドはconsole.log が消える場合あり
- 本番ではHermes が strict。プロパティアクセスや循環参照で別のエラーになることも
- OTA 更新でJS と Native のバージョン不整合(古いネイティブに新しい JS が乗る)
- release configuration でテストするのを忘れない
Expo Go の限界
Expo Go はExpo SDK 内のモジュールしか含まない。 以下を入れたい時点で Expo Go は使えない、Dev Build が必要:
react-native-mmkvreact-native-firebase@stripe/stripe-react-native- 独自の native module / config plugin
- app.json で plugin の追加 options を変えた時
SDK アップグレード
Expo SDK は数ヶ月ごとにメジャーバージョンアップ。RN のバージョンや React のバージョンも一緒に上がる。
- 毎回 公式 changelog を読む
- 2 SDK 以上遅れないのが運用の目安。離れすぎると追従が大変
npx expo install --fixで互換バージョンに揃う- 非互換変更(renamed API、config plugin の変更)を確認
- 本番デプロイの前に必ず Dev Build で動作確認
セキュリティ
- 機密はクライアントに書かない。JS バンドルは展開可能(API キーや署名キーは置かない)。
- 機密が必要ならサーバ側でやる。クライアントには「短命トークン」を渡す。
- ローカル保存は
expo-secure-store(OS のキーチェーン)。 - OAuth は
expo-auth-session+ PKCE。 - App Transport Security を有効に保つ(HTTPS のみ)。
Deep Link / URL Scheme
app.jsonのschemeを必ず設定(例:"myapp")。- Expo Go ではスキームが Expo Go のものになるので、Dev Build で本番動作を確認。
- Universal Link / App Link を使うなら、サーバ側に
apple-app-site-association/assetlinks.jsonが必要。
サードパーティサービス連携
- Sentry — クラッシュ監視。
@sentry/react-native+ Expo plugin で簡単。 - RevenueCat — アプリ内課金の SaaS ラッパ。RN サブスクの定番。
- Stripe — モバイル決済。
@stripe/stripe-react-native。 - Supabase / Firebase — BaaS。RN SDK あり。
- OneSignal / Pusher Beams — プッシュ通知の高度な配信管理。
App Store / Google Play 審査の罠
- パーミッション文言の不備: 何のために使うかを書く
- ログインボタンが Apple Sign-In を含まない(iOS で Google/FB 等を出すなら必須)
- サブスクの説明不足: 価格・更新タイミング・解約方法を画面内に明示
- テスト用アカウントの提供: 審査用の認証情報を Connect に書く
- クラッシュバグ: バックグラウンド復帰、初回起動などで落ちる
- Android のターゲット SDK バージョン: Google Play は毎年要件を上げてくる
- 個人情報の取り扱い宣言: アプリ内データの扱いを Apple/Google の管理画面に申告
テスト戦略
- 単体テスト:
jest+@testing-library/react-native - E2E: Maestro(YAML で書ける、シンプル) / Detox / Appium
- Snapshot: コンポーネントの構造を比較。慎重に使う(フラフラ更新になる)。
- 実機テスト: ローエンド Android、古い iPhone を1台ずつ持つと品質が安定する
運用
- クラッシュ監視(Sentry)を最初から入れる
- EAS Update でホットフィックスのフローを練習する
- EAS の channel 設計を「production」「preview」「dev」に分ける
- 機能フラグ(LaunchDarkly / Statsig / 自前)で機能オン/オフ切替
- 強制アップデート画面を持つ(重大な変更がある時)
判断ガイド
| 状況 | 選択 |
|---|---|
| 新規プロジェクト | Expo + Expo Router + TypeScript + EAS |
| 状態管理 | useState + Zustand + React Query |
| 永続化 | SecureStore(機密)+ MMKV(普通)+ SQLite(複雑) |
| スタイル | StyleSheet または NativeWind |
| ナビ | Expo Router |
| アニメ | Reanimated + Gesture Handler |
| リスト | FlashList |
| 画像 | expo-image |
| ビルド/配布 | EAS Build / Submit / Update |
| クラッシュ監視 | Sentry |
公式ドキュメントを読むのが断然早い:
- Expo Docs
- React Native Docs
- React Navigation Docs
- Reanimated Docs
困ったらExpo DiscordとX(旧Twitter)の #expo, #reactnative も活発。