React Nativeプロジェクト構成とは?src・android・ios・package.json・Metro・TypeScriptを徹底解説
React Nativeは、Reactの考え方を使ってAndroidとiOSのネイティブアプリを開発できるフレームワークです。React Native公式サイトでも、Reactを使ってAndroid、iOSなどのネイティブアプリを作成できることが示されています。Flutterと同じくクロスプラットフォーム開発に使われますが、React NativeではJavaScriptまたはTypeScriptで書くアプリ層と、Android・iOSのネイティブ層が共存する点を理解することが特に重要です。画面や状態管理はJavaScript側で実装することが多い一方、ビルド、署名、権限、ネイティブSDK、Deep Link、Push通知などではAndroidやiOSの設定も必要になります。
現在のReact NativeではTypeScriptの重要性も高くなっています。React Native公式ドキュメントでは、React Native CLIや主要テンプレートで作成される新規プロジェクトはTypeScriptを標準的に使うと説明されています。つまり、実務でReact Nativeプロジェクト構成を考えるときは、JavaScriptだけでなく、tsx、tsconfig.json、型定義、画面間パラメータ型、APIレスポンス型まで含めて設計する必要があります。TypeScriptを前提に構成すれば、画面遷移、props、APIデータ、状態管理の安全性を高めやすくなります。
本記事では、React Nativeプロジェクト構成を20の大きな観点に分けて詳しく解説します。初心者が最初に理解すべき src、components、screens、navigation、services、package.json、android、ios の役割から、実務で重要になるTypeScript、Metro、Babel、状態管理、API層、Native Modules、テスト、CI/CD、セキュリティ、長期保守しやすい設計まで、React Native開発で役立つ構成の考え方を体系的に整理します。
1. React Nativeプロジェクト構成の全体像
React Nativeプロジェクト構成を理解する第一歩は、React NativeアプリがJavaScript・TypeScriptだけで完結しているわけではないと知ることです。画面、状態管理、API通信、ナビゲーション、共通コンポーネントはJavaScript側で作ることが多いですが、AndroidとiOSのネイティブプロジェクトも同じリポジトリ内に存在します。つまり、React Nativeプロジェクトは「ReactのUI構成」と「モバイルネイティブ開発の構成」が重なった形だと考えると理解しやすくなります。
1.1 React NativeはJavaScript層とネイティブ層を持つ
React Nativeプロジェクトでは、src 配下にTypeScriptまたはJavaScriptのアプリコードを置き、android と ios 配下に各プラットフォームのネイティブ設定を持ちます。UIや画面状態の多くはReactコンポーネントで実装できますが、AndroidのGradle、AndroidManifest、iOSのXcode、Info.plist、Podfile、署名設定などは別途存在します。初心者は App.tsx や src だけを見てしまいがちですが、実務ではネイティブ側の構成も頻繁に関係します。Push通知、Firebase、カメラ、位置情報、Deep Link、決済、認証SDKなどを扱う場合、JavaScript側とネイティブ側の両方を確認する必要があります。
1.2 srcはアプリ本体の中心になる
React Nativeアプリ本体のコードは、実務では src フォルダにまとめることが一般的です。screens、components、navigation、services、hooks、store、types、utils などを src 配下に分けることで、アプリの構成が分かりやすくなります。小さなサンプルでは App.tsx だけでも動きますが、画面数が増えると1ファイルや浅い階層だけではすぐに限界が来ます。src を中心に責務を分けることで、UI、状態管理、通信、型定義、共通処理の境界が明確になります。React Nativeプロジェクトの保守性は、src の整理方針に大きく左右されます。
1.3 androidとiosはネイティブ設定の入口になる
React Nativeでは、android フォルダにAndroid Studio向けのプロジェクトがあり、ios フォルダにXcode向けのプロジェクトがあります。AndroidではGradle、Manifest、KotlinまたはJavaコード、署名設定が関係し、iOSではXcode Target、Info.plist、Podfile、署名、Capabilitiesが関係します。JavaScript側でライブラリを追加しただけでも、AndroidまたはiOS側で追加設定が必要になる場合があります。React Nativeはクロスプラットフォーム開発を効率化しますが、ネイティブ層を完全に隠すものではありません。構成を理解するには、JavaScript層とネイティブ層をセットで見る必要があります。
1.4 package.jsonは依存関係と実行コマンドを管理する
React Nativeプロジェクトでは、package.json が依存関係、スクリプト、プロジェクト情報を管理する中心的なファイルになります。React Native本体、React、ナビゲーション、状態管理、テスト、Lint、TypeScript、Babel、Metro関連のパッケージなどがここで管理されます。また、start、android、ios、test、lint、typecheck などのスクリプトも定義できます。package.jsonが整理されていると、開発者が同じコマンドで起動、ビルド、テスト、解析を実行しやすくなります。逆にスクリプトが散らばっていると、チーム内で手順が属人化しやすくなります。
1.5 MetroはJavaScriptとassetsのビルドを担当する
React Native公式ドキュメントでは、React NativeはJavaScriptコードとassetsをビルドするためにMetroを使うと説明されています。Metroは、React NativeアプリのJavaScriptバンドル生成、モジュール解決、assets処理、開発時の更新に関わる重要な仕組みです。metro.config.js を変更することで、monorepo、SVG変換、alias、watchFolders、resolverなどを調整することがあります。Metroの設定を理解していないと、ファイルが見つからない、assetsが読み込めない、monorepoで依存解決が失敗する、といった問題に対応しにくくなります。
1.6 構成理解はクロスプラットフォーム品質に直結する
React Nativeプロジェクト構成を理解することは、単にフォルダ名を覚えることではありません。JavaScript層、TypeScript型、Metro、Babel、Android、iOS、Native Modules、テスト、CI/CDがどのように連携して最終的なアプリになるのかを理解することです。構成が整理されていれば、Androidだけで起きる不具合、iOSだけで起きる不具合、JavaScript側の状態管理の問題を切り分けやすくなります。React Nativeは1つのコードベースで複数プラットフォームへ対応できますが、その強みを活かすにはプロジェクト構成の設計が欠かせません。
2. srcフォルダの役割
src フォルダは、React Nativeアプリの本体コードを置く中心的な場所です。テンプレートによっては最初から存在しない場合もありますが、実務では src を作成してアプリコードをまとめる構成がよく使われます。
2.1 srcはアプリコードの集約場所になる
React Nativeプロジェクトでは、ルート直下に App.tsx が置かれることが多いですが、実務ではアプリ本体のほとんどを src に集約します。src の中に画面、共通UI、ナビゲーション、状態管理、API通信、型定義、ユーティリティを分けることで、プロジェクト全体の見通しが良くなります。もしルート直下に多数のファイルを置き続けると、設定ファイル、ネイティブフォルダ、アプリコードが混ざり、何がアプリ本体なのか分かりにくくなります。src は、React Nativeアプリの機能コードを整理するための基本的な境界として考えるべきです。
2.2 screensは画面単位のコードを置く
screens には、LoginScreen、HomeScreen、ProfileScreen、SettingsScreenのような画面単位のコンポーネントを置きます。画面はナビゲーションの単位になりやすく、ユーザーが直接見る大きなUI構造を表します。画面ごとにファイルを分けると、機能追加や画面修正の対象を探しやすくなります。ただし、ScreenにAPI通信、状態管理、フォーム処理、共通UIまで全部書くと、画面ファイルが肥大化します。Screenは画面全体の構成と表示に集中させ、細かいUI部品はcomponentsへ、状態管理はhooksやstoreへ、通信はservicesへ分けるのが実務的です。
2.3 componentsは再利用UIを置く
components には、複数画面で使う共通UI部品を置きます。Button、TextInput、Card、Header、Avatar、LoadingIndicator、ErrorMessageなどが代表例です。共通コンポーネントを整理しておくと、アプリ全体の見た目を統一しやすくなり、デザイン変更にも強くなります。ただし、すべてを最初から共通化すると、propsが増えすぎて使いにくいコンポーネントになることがあります。実務では、複数画面で実際に重複したもの、デザインルールとして安定しているものから共通化するとよいです。
2.4 navigationは画面遷移を管理する
navigation には、Stack、Tab、Drawer、認証前後のルート、Deep Link設定など、画面遷移に関するコードをまとめます。画面ごとに遷移定義を散らばらせると、アプリ全体の画面構造が見えにくくなります。特に、ログイン状態によって表示するStackを切り替える場合や、タブごとに別Stackを持つ場合は、navigation構成を整理する必要があります。画面遷移はアプリ体験の骨格なので、navigation フォルダを独立させ、ルート定義、型定義、Deep Link設定を一元管理すると保守しやすくなります。
2.5 servicesは外部処理との接点になる
services には、API通信、認証、Push通知、Analytics、Storage、外部SDK連携などを置くことがあります。画面コンポーネントから直接fetchやaxiosを呼ぶと、通信処理が画面に密結合し、テストや変更が難しくなります。servicesへ外部処理を分けることで、UI側は表示とユーザー操作に集中できます。たとえば、AuthService、UserService、NotificationService、AnalyticsServiceのように用途ごとに分けると分かりやすくなります。ただし、servicesが巨大化しないよう、RepositoryやAPI clientと役割を分けることも重要です。
2.6 srcは規模に合わせて育てる
src の構成に唯一の正解はありません。小規模アプリでは screens、components、services、utils 程度で十分な場合があります。中規模以上になると、features、shared、core、store、types、api、hooks などを追加して整理する価値が出てきます。最初から過度に複雑にする必要はありませんが、無秩序にファイルを増やすと後から整理が大変です。React Nativeプロジェクトでは、アプリの成長に合わせて src の階層を段階的に育てることが大切です。
3. App.tsxとアプリ起動構成
App.tsx はReact Nativeアプリの入口になる重要なファイルです。ここにすべてを詰め込むのではなく、アプリ全体のProvider、Navigation、Theme、初期化処理を整理して配置することが重要です。
3.1 App.tsxはアプリ全体の入口になる
React Nativeプロジェクトでは、App.tsx または App.js がアプリ本体の入口として使われます。ここからNavigation Container、Theme Provider、State Provider、SafeAreaProvider、GestureHandlerRootViewなどを組み合わせ、アプリ全体のルート構造を作ります。初心者はApp.tsxに画面実装を直接書きがちですが、実務ではApp.tsxを薄く保つほうが保守しやすくなります。App.tsxは画面を大量に書く場所ではなく、アプリ全体の土台を組み立てる場所として扱うべきです。
3.2 Provider構成を整理する
React Nativeアプリでは、状態管理、テーマ、認証状態、言語設定、Query Client、Safe Areaなど、複数のProviderを使うことがあります。これらをApp.tsxに無秩序に並べると、起動構成が読みにくくなります。実務では、AppProviders.tsx のようなファイルを作り、Providerのネストを整理することがあります。Providerの順序によって動作が変わる場合もあるため、依存関係を理解して配置する必要があります。Provider構成を整理すると、テスト時に必要なProviderだけを差し替えることも容易になります。
3.3 Navigation Containerは入口で管理する
React Navigationなどを使う場合、Navigation Containerはアプリ全体のルートに配置することが多いです。App.tsxやRootNavigatorでNavigation Containerを管理し、その中に認証前Stack、認証後Stack、Tab Navigatorなどを配置します。Navigation Containerを複数箇所に分散させると、Deep Linkや画面遷移の状態管理が複雑になります。アプリ全体の画面構造は一箇所で把握できるようにするのが理想です。Navigationはユーザー体験の骨格なので、入口構成と強く関係します。
3.4 初期化処理を分離する
アプリ起動時には、Push通知初期化、Analytics設定、Storageからの認証状態読み込み、Remote Config取得、言語設定読み込みなどが必要になる場合があります。これらをApp.tsxに直接書くと、入口ファイルが肥大化します。実務では、bootstrap.ts、initializeApp.ts、useAppInitialization.ts のように初期化処理を分けることがあります。初期化処理を分離しておけば、起動中画面やエラー時の表示も整理しやすくなります。アプリ起動は安定性に関わるため、構成上も明確に管理するべきです。
3.5 Error Boundaryを検討する
Reactアプリと同じく、React NativeでもError Boundaryを使って一部のレンダリングエラーを捕捉することがあります。すべてのエラーを防げるわけではありませんが、画面全体が真っ白になることを避けたり、エラー画面を表示したりするために役立ちます。クラッシュレポートサービスと組み合わせる場合、どの層でエラーを捕捉するかを設計する必要があります。App.tsx付近にError Boundaryを置くことで、アプリ全体の安全網を作れます。ただし、エラーを隠すだけでなく、原因を記録して修正できる運用も重要です。
3.6 App.tsxはテストしやすくする
App.tsxが複雑すぎると、テストが難しくなります。外部サービス初期化、ネイティブ依存、非同期処理、Provider、Navigationがすべて混ざっていると、単純なレンダリングテストでも失敗しやすくなります。実務では、App本体とProvider構成、初期化処理を分け、テスト用にはMock ProviderやTestAppを用意することがあります。アプリ入口は実行時だけでなく、テスト時にも扱いやすい構成にするべきです。App.tsxを薄く保つことは、テスト容易性にも直結します。
4. package.jsonの役割
package.json は、React Nativeプロジェクトの依存関係、スクリプト、ツール設定の中心です。Node.js系プロジェクトとしてのReact Nativeを理解するには、このファイルの役割を押さえる必要があります。
4.1 package.jsonは依存関係の中心になる
React Nativeプロジェクトでは、React Native本体、React、ナビゲーション、状態管理、HTTPクライアント、フォーム管理、テスト、Lint、TypeScript関連など、多くのパッケージをpackage.jsonで管理します。依存関係は便利ですが、追加するたびに保守責任が増えます。更新頻度、互換性、メンテナンス状況、ネイティブ連携の有無を確認せずにライブラリを増やすと、将来的にアップグレードやビルドで苦労します。package.jsonは単なる一覧ではなく、プロジェクトがどの外部資産に依存しているかを示す重要な設計情報です。
4.2 dependenciesとdevDependenciesを分ける
dependencies にはアプリ実行時に必要なパッケージを置き、devDependencies には開発時だけ必要なパッケージを置きます。たとえば、React Native本体やNavigation、状態管理ライブラリはdependenciesに入ることが多く、TypeScript、Jest、ESLint、Prettier、Babel関連はdevDependenciesに入ることが多いです。分類が曖昧だと、プロジェクトの意図が見えにくくなります。実務では、パッケージを追加するときに「実行時に必要か」「開発時だけか」を確認する習慣が重要です。依存関係の整理は、保守性とビルド安定性に関わります。
4.3 scriptsは開発手順を標準化する
package.json のscriptsには、start、android、ios、test、lint、typecheck、clean などのコマンドを定義できます。チーム開発では、よく使う操作をscriptsへまとめることで、開発者ごとの手順差を減らせます。たとえば、npm run typecheck でTypeScript確認、npm run lint で静的解析、npm test でテストを実行できるようにしておくと、CIにも組み込みやすくなります。scriptsが整理されているプロジェクトは、新しいメンバーが参加したときにも立ち上がりが早くなります。
4.4 package-lockやyarn.lockを管理する
React Nativeプロジェクトでは、npmなら package-lock.json、Yarnなら yarn.lock、pnpmなら pnpm-lock.yaml が生成されます。これらは依存関係の具体的なバージョン解決結果を固定する重要なファイルです。lockfileをGit管理することで、開発者やCIが同じ依存関係を使いやすくなります。もしlockfileを管理しなければ、環境ごとに違うバージョンがインストールされ、ローカルでは動くのにCIで失敗する可能性があります。依存関係の再現性は、React Native開発の安定性に直結します。
4.5 React Nativeのアップグレードに影響する
React Nativeは、React Native本体だけでなく、React、Metro、Babel、Android Gradle Plugin、CocoaPods、ネイティブ設定など複数の要素と関係します。package.jsonの依存関係が古くなると、アップグレード時の差分が大きくなります。特にネイティブ連携ライブラリは、React Nativeのバージョンに強く依存する場合があります。定期的に依存関係を確認し、小さく更新していくことが長期保守では重要です。React Nativeプロジェクトでは、package.jsonの管理がアップグレード戦略にも直結します。
4.6 不要なパッケージは削除する
プロジェクトが長く続くと、昔使っていたライブラリがpackage.jsonに残ったままになることがあります。不要な依存関係は、インストール時間、ビルド時間、脆弱性リスク、アップグレード負担を増やします。定期的に依存関係を棚卸しし、使っていないパッケージを削除することが重要です。特にReact Nativeでは、ネイティブコードを含むライブラリが残っているとAndroidやiOSのビルドにも影響する場合があります。package.jsonは軽く保つほど、プロジェクトの保守が楽になります。
5. TypeScript構成
現在のReact Native実務では、TypeScriptを前提にプロジェクトを構成することが一般的です。型定義を適切に使うことで、props、navigation、APIレスポンス、状態管理の安全性を高められます。
5.1 TypeScriptは新規プロジェクトの標準になっている
React Native公式ドキュメントでは、React Native CLIや主要テンプレートで作成される新規プロジェクトはTypeScriptを標準的に使うと説明されています。これにより、App.tsx、tsconfig.json、型定義ファイルなどが初期構成として含まれることがあります。TypeScriptを使うと、コンポーネントprops、画面遷移パラメータ、APIレスポンス、状態管理の型を明確にできます。大規模アプリでは、型があることでリファクタリングや機能追加の安全性が高まります。React Nativeプロジェクト構成では、TypeScriptを単なるオプションではなく中心要素として扱うべきです。
5.2 tsconfig.jsonは型チェックの基本設定になる
tsconfig.json は、TypeScriptの型チェックやコンパイル設定を管理するファイルです。strict設定、path alias、対象ファイル、除外ファイル、React JSX設定などが関係します。設定が緩すぎるとTypeScriptの利点が弱まり、設定が厳しすぎると移行や開発負担が大きくなる場合があります。実務では、最初は現実的な厳しさで始め、徐々にstrict化していく方法もあります。tsconfigはプロジェクト全体の型安全性に関わるため、変更時にはチームでレビューするべきです。
5.3 propsの型を明確にする
React Nativeでは、コンポーネントのpropsを型定義することで、使い方のミスを減らせます。たとえば、Buttonコンポーネントに必要なtitle、onPress、disabled、variantなどを型で定義すれば、利用側で不足や誤った値を検出できます。共通コンポーネントほどprops型が重要になります。propsが曖昧だと、画面ごとに異なる使い方をされ、コンポーネントが不安定になります。TypeScriptを使うなら、UI部品のAPIとしてprops型を丁寧に設計することが重要です。
5.4 navigationの型を管理する
React Navigationなどを使う場合、画面名とパラメータの型を定義すると安全性が高まります。存在しない画面へ遷移する、必要なパラメータを渡し忘れる、型の違う値を渡すといったミスを減らせます。大規模アプリでは、Stackごと、TabごとにParamListを定義し、画面Propsへ反映する構成がよく使われます。navigation型が整理されていると、画面追加や遷移変更の影響をTypeScriptで追いやすくなります。画面遷移はアプリ全体に関わるため、型安全性の価値が大きい領域です。
5.5 APIレスポンス型を分ける
APIレスポンスをTypeScriptで型定義すると、データ利用時の安全性が高まります。ただし、APIレスポンス型をそのまま画面表示用の型として使うと、外部仕様変更の影響がUIまで広がります。実務では、API DTO型、Domain Model型、View Model型を分けることがあります。API側のsnake_caseやnullableな値を、アプリ内部で扱いやすい型に変換すれば、UI側の条件分岐が減ります。TypeScriptは型を付けるだけでなく、データ境界を明確にするためにも使うべきです。
5.6 anyを増やさない運用が重要
TypeScriptを導入しても、any を多用すると型安全性は大きく下がります。急いで実装すると、APIレスポンスや外部ライブラリ周りでanyを使いたくなることがありますが、長期的にはバグの温床になります。完全にanyをゼロにするのが難しい場合でも、利用範囲を限定し、型変換や検証を行う場所を決めるべきです。unknown、type guard、schema validationなどを使うことで、安全に外部データを扱えます。TypeScript構成は、書くだけでなく運用ルールが重要です。
6. components構成
React Nativeでは、UIをコンポーネントとして分けることが基本です。共通コンポーネントを適切に設計すると、画面実装の重複を減らし、デザインの一貫性を保ちやすくなります。
6.1 componentsは共通UIの置き場所になる
components フォルダには、アプリ内で複数回使うUI部品を置きます。ボタン、入力欄、カード、ヘッダー、リスト項目、ローディング表示、エラー表示などが代表的です。共通UIをcomponentsへ切り出すと、同じ見た目や挙動を複数画面で再利用できます。画面ごとに似たようなボタンや入力欄を作ると、デザイン差分が生まれ、修正時に漏れが発生します。componentsは、UI品質と開発効率を支える重要なフォルダです。
6.2 atomsやmoleculesに分ける場合もある
大規模アプリでは、Design Systemの考え方に合わせて、componentsをatoms、molecules、organismsのように分けることがあります。atomsにはTextやButtonのような最小部品、moleculesにはSearchBoxやUserCardのような小さな組み合わせ、organismsにはHeaderやFormSectionのような大きめの部品を置きます。ただし、この分類はチームが理解していないと逆に混乱します。小規模アプリでは、無理にAtomic Designを採用する必要はありません。分類は目的ではなく、探しやすさと再利用性を高めるための手段です。
6.3 画面固有componentsと共通componentsを分ける
すべての小さなUIをグローバルなcomponentsに置くと、共通部品フォルダが肥大化します。特定画面でしか使わない部品は、そのscreenやfeatureの近くに置くほうが分かりやすい場合があります。たとえば、LoginScreen専用のPasswordStrengthViewは features/auth/components に置き、全画面で使うPrimaryButtonは shared/components に置くようにします。共通化の基準を明確にすると、componentsフォルダが何でも置き場になるのを防げます。部品の利用範囲に応じて配置を変えることが重要です。
6.4 props設計が使いやすさを決める
共通コンポーネントは、props設計が使いやすさを大きく左右します。variant、size、disabled、loading、icon、onPressなどをどう定義するかによって、利用側のコードがシンプルにも複雑にもなります。propsを増やしすぎると万能コンポーネントになり、内部条件分岐が複雑になります。逆にpropsが少なすぎると、画面ごとにラップコンポーネントが増えます。実務では、デザインシステムに沿った範囲でpropsを設計し、使い方をStorybookやドキュメントで共有するとよいです。
6.5 styleをコンポーネント内に閉じる
React Nativeでは、StyleSheetやinline styleを使ってUIを調整します。共通コンポーネントでは、基本スタイルをコンポーネント内に閉じ、利用側は必要な範囲でstyle overrideできるようにする構成がよく使われます。ただし、style overrideを許しすぎると、共通部品なのに画面ごとに見た目がバラバラになります。Design Systemの部品では、自由度と統一性のバランスが重要です。どこまでカスタマイズを許すかを決めておくと、長期的にUI品質を保ちやすくなります。
6.6 componentsはテストやプレビューと相性がよい
共通コンポーネントは、単体でテストやプレビューを用意しやすいです。React Native Testing Libraryで表示や操作を確認したり、Storybookで状態ごとの見た目を確認したりできます。たとえば、Buttonの通常状態、disabled状態、loading状態、icon付き状態を個別に確認しておけば、画面側で安心して使えます。共通コンポーネントは多くの画面で使われるため、小さな不具合が広範囲に影響します。components構成では、再利用だけでなく品質確認の仕組みもセットで考えることが重要です。
7. screensとfeatures構成
React Nativeアプリが大きくなると、画面単位だけではなく、機能単位でコードを整理する必要が出てきます。screensとfeaturesの使い分けを理解すると、保守しやすい構成を作れます。
7.1 screensは画面の入口になる
screens は、ユーザーが実際に表示する画面単位のコンポーネントを置く場所です。LoginScreen、HomeScreen、ProductDetailScreen、SettingsScreenのように、Navigationから呼び出される単位になります。screenは画面全体のレイアウト、状態の読み取り、イベントの受け渡しを担当します。ただし、画面内の細かいUIやロジックをすべてscreenに書くと、ファイルが長くなります。screenは画面全体の構成役として薄く保ち、詳細なUI部品や処理は別ファイルへ分けるのが理想です。
7.2 featuresは機能単位でまとめる
features 構成では、auth、home、profile、cart、settingsのように機能単位でコードをまとめます。各featureの中にscreens、components、hooks、services、types、storeなどを置くことで、その機能に関係するファイルを一箇所で管理できます。機能が大きくなるアプリでは、features構成のほうが探しやすくなります。たとえば、認証周りの画面、API、型、状態管理を features/auth にまとめれば、ログイン機能の修正時に関連ファイルを追いやすくなります。チームごとに機能を担当する場合にも有効です。
7.3 screensだけの構成は小規模向き
小規模アプリでは、src/screens にすべての画面を置き、共通UIを src/components、APIを src/services に置く構成でも十分です。画面数が少ない場合、features構成にすると階層が深くなりすぎることがあります。重要なのは、現在の規模に合った構成を選ぶことです。ただし、screensだけの構成で始めても、将来的に機能が増える可能性があるなら、画面ファイルを大きくしすぎないようにするべきです。後からfeaturesへ移行しやすいように、画面固有部品を近くに置くなどの工夫ができます。
7.4 feature内componentsを活用する
feature内でしか使わないUI部品は、グローバルcomponentsではなくfeature内componentsへ置くと分かりやすくなります。たとえば、features/auth/components/SocialLoginButton.tsx や features/cart/components/CartItem.tsx のような構成です。これにより、共通componentsフォルダが肥大化するのを防げます。また、機能削除や大幅変更のときに、関連部品をまとめて扱いやすくなります。部品の利用範囲に応じて配置を決めることが、React Nativeプロジェクトの整理に役立ちます。
7.5 feature間依存に注意する
features構成では、feature同士が直接依存しすぎないように注意が必要です。たとえば、cart featureがprofile featureの内部型やhooksを直接参照すると、機能間の結合が強くなります。共通で使う型や処理はsharedやcoreへ移すべきです。featureは独立性を高めるための構成ですが、依存ルールがなければ逆に複雑になります。実務では、featureからsharedやcoreへ依存するのは許可し、feature同士の直接依存は避けるといったルールを決めることがあります。
7.6 features構成はチーム開発に向いている
複数人や複数チームでReact Nativeアプリを開発する場合、features構成は担当範囲を明確にしやすいです。認証チーム、決済チーム、プロフィールチームのように機能ごとに担当を分ける場合、ファイル配置も機能単位になっていると作業しやすくなります。Pull Requestの影響範囲も分かりやすく、レビュー担当者も関連ファイルを追いやすくなります。大規模アプリでは、features構成がコード整理だけでなく、チーム運用にも良い影響を与えます。
8. navigation構成
React Nativeアプリでは、画面遷移をどのように管理するかが重要です。Navigation構成が整理されていると、認証フロー、タブ、Stack、Deep Link、画面パラメータを安全に扱えます。
8.1 navigationはアプリの画面構造を表す
navigation構成は、アプリの画面構造を表します。ログイン前に表示する画面、ログイン後のメイン画面、タブ構成、詳細画面、設定画面、モーダル表示などを管理します。画面ごとに遷移処理を直接書くと、アプリ全体の構造が見えにくくなります。navigation フォルダにRootNavigator、AuthNavigator、MainTabNavigator、Screen Paramsなどをまとめると、どの画面がどこに属するのかが分かりやすくなります。画面遷移はユーザー体験の骨格なので、プロジェクト構成の中でも重要な領域です。
8.2 Stack Navigatorは画面階層を管理する
Stack Navigatorは、画面を積み重ねるように遷移する構成に向いています。一覧画面から詳細画面へ進み、戻るボタンで戻るような一般的な流れで使われます。実務では、AuthStack、HomeStack、ProfileStackのように機能ごとにStackを分けることがあります。Stackが大きくなりすぎると、どの画面がどの流れに属するのか分かりにくくなります。画面数が増えたら、機能単位やタブ単位でStackを分けると保守しやすくなります。
8.3 Tab Navigatorは主要機能を分ける
Tab Navigatorは、Home、Search、Notification、Profileのような主要機能を切り替えるために使われます。各タブが独自のStackを持つこともあり、ユーザーがタブを切り替えてもそれぞれの画面状態を保持したい場合に有効です。Tab構成では、タブごとの責務を明確にし、不要にタブ間で状態を共有しすぎないことが重要です。共通状態はstoreやcontextで管理し、各タブは自分の画面遷移に集中させると整理しやすくなります。Tabはアプリの主要構造なので、初期設計が重要です。
8.4 認証状態でNavigatorを切り替える
多くのアプリでは、未ログイン状態ではLoginやSignupを表示し、ログイン済み状態ではMain Appを表示します。この切り替えはRootNavigatorで管理することが多いです。認証状態を画面ごとに判定して遷移させると、条件分岐が散らばり、予期しない画面表示が起こりやすくなります。RootNavigatorで認証状態を見て、AuthStackとAppStackを切り替える構成にすると、画面構造が明確になります。認証フローはアプリ全体に関わるため、navigation構成の中心として扱うべきです。
8.5 navigation型でパラメータを安全にする
TypeScriptを使う場合、Navigation Param Listを定義して画面間パラメータを型安全にできます。たとえば、ProductDetailScreenにはproductIdが必須、ProfileScreenにはuserIdが任意というように型で表現できます。これにより、必要なパラメータを渡し忘れるミスや、型の違う値を渡すミスを減らせます。画面名も型で管理できるため、存在しない画面名を使う問題も検出しやすくなります。React NativeでTypeScriptを使うなら、navigation型は非常に価値が高い設計要素です。
8.6 Deep Linkはnavigationと統合する
Deep LinkやUniversal Linkを使う場合、外部URLからアプリ内の特定画面へ遷移する必要があります。これはJavaScript側のnavigation設定だけでなく、Android ManifestやiOS Associated Domains、Info.plist設定とも関係します。navigation側では、URLパスと画面の対応、パラメータ解析、認証状態によるリダイレクトを管理します。外部入力を扱うため、存在しないIDや不正なパラメータへの対応も必要です。Deep Linkは便利ですが、構成を整理しないとバグやセキュリティ問題の原因になります。
9. state management構成
React Nativeアプリでは、状態管理の設計がプロジェクト構成に大きく影響します。画面内状態、アプリ全体状態、サーバー状態、フォーム状態を分けて考えると整理しやすくなります。
9.1 状態管理はUI肥大化を防ぐ
React Nativeでは、useStateやuseReducerを使って画面内状態を管理できます。しかし、API通信結果、認証状態、カート情報、ユーザー設定、フォーム入力、エラー状態をすべて画面コンポーネントに書くと、screenが肥大化します。状態管理をhooks、store、context、query libraryなどへ分けることで、UIとロジックを分離できます。状態管理の目的は、流行のライブラリを使うことではなく、状態の所在と更新方法を明確にすることです。状態の責務が明確なプロジェクトは、機能追加や不具合調査がしやすくなります。
9.2 local stateとglobal stateを分ける
すべての状態をglobal storeに入れる必要はありません。入力欄の一時値、モーダルの開閉、選択中タブなどは画面内のlocal stateで十分な場合があります。一方、認証状態、ユーザー情報、テーマ、言語、カート、通知設定などはglobal stateとして管理する価値があります。local stateとglobal stateの区別が曖昧だと、不要にアプリ全体へ依存が広がります。状態の寿命、利用範囲、更新頻度を考えて配置を決めることが重要です。状態は広げるほど管理コストが増えると考えるべきです。
9.3 Contextは軽い共有状態に向いている
React Contextは、テーマ、認証状態、言語設定など、比較的軽い共有状態に使いやすい仕組みです。ただし、大量の頻繁に変わる状態をContextで管理すると、再レンダリングの問題が起こる場合があります。Contextは便利ですが、アプリ全体の状態管理を何でも詰め込む場所ではありません。Context ProviderをApp.tsxに大量に並べる場合は、Provider構成を整理する必要があります。実務では、Contextは設定や低頻度更新の状態に使い、複雑な状態は専用storeやserver-state管理ライブラリに任せることがあります。
9.4 ReduxやZustandなどのstoreを使う場合
Redux、Zustand、Jotai、MobXなどの状態管理ライブラリを使う場合、storeの配置、sliceの分け方、selector、middleware、永続化の方針を決める必要があります。Reduxは大規模で明示的な状態管理に向き、Zustandは軽量でシンプルなglobal storeに向くことがあります。ライブラリ選定はチームの理解度とアプリ規模に合わせるべきです。storeを導入しても、状態の設計が悪ければ複雑さは解決しません。重要なのは、どの状態をどこで持ち、誰が更新するかを明確にすることです。
9.5 server stateは専用ライブラリで扱うと整理しやすい
APIから取得するデータは、local UI stateとは性質が違います。キャッシュ、再取得、ローディング、エラー、ページネーション、stale dataなどを扱う必要があります。React QueryやSWR系の考え方を使うと、server stateをglobal storeへ手動で詰め込まずに管理しやすくなります。server stateを通常のUI stateと混ぜると、キャッシュ更新や再取得が複雑になります。実務では、フォームやUI状態はlocalまたはstore、APIデータはquery libraryというように役割分担する構成が有効です。
9.6 状態管理はテスト可能にする
状態管理のコードは、Unit Testやcomponent testの対象になります。storeのaction、reducer、selector、custom hookをテストできるようにすると、画面全体を起動しなくてもロジックを確認できます。状態管理がscreenに密結合していると、テストはUIテスト寄りになり、重くなります。状態更新ロジックをhooksやstoreへ切り出し、外部依存をmockできる構成にすると、テストしやすくなります。状態管理はアプリの動作を支える中心なので、設計とテストをセットで考えるべきです。
10. API・services構成
React Nativeアプリが外部APIを利用する場合、API client、services、repository、型定義、エラーハンドリングを整理することが重要です。画面から直接APIを呼ぶ構成は簡単ですが、長期的には保守しにくくなります。
10.1 API層は画面から分離する
画面コンポーネントから直接fetchやaxiosを呼ぶと、UIと通信処理が密結合します。API URL、ヘッダー、認証トークン、エラー処理、リトライ、タイムアウトなどが画面に散らばり、変更が難しくなります。実務では、api、services、repositories のようなフォルダを作り、通信処理を画面から分離します。画面は「データを取得する」という操作だけを呼び、通信の詳細はAPI層へ隠すのが理想です。この分離により、API仕様変更やライブラリ変更の影響を小さくできます。
10.2 api clientを共通化する
API clientは、base URL、共通headers、認証トークン、タイムアウト、レスポンス処理、エラーハンドリングをまとめる場所です。axiosを使う場合はaxios instanceを作り、fetchを使う場合はラッパー関数を作ることがあります。共通API clientがあれば、各serviceで毎回同じ設定を書く必要がありません。認証トークンの付与や401エラー時の処理も一箇所で管理しやすくなります。ただし、共通clientにビジネスロジックを入れすぎると肥大化するため、責務を明確にすることが重要です。
10.3 servicesは機能ごとのAPIを管理する
UserService、AuthService、ProductService、OrderServiceのように、機能ごとにAPI呼び出しをまとめる構成がよく使われます。serviceはAPI clientを使って具体的なエンドポイントを呼び出し、レスポンスを型付きで返します。画面や状態管理コードはserviceを通してデータを取得するため、API URLやHTTP methodを直接知る必要がありません。serviceが整理されていると、API追加や変更の場所が分かりやすくなります。機能ごとにserviceを分けることで、巨大なapiファイルになるのを防げます。
10.4 DTOとUI modelを分ける
APIレスポンスの型をそのままUIで使うと、外部仕様の変更が画面に直接影響します。たとえば、APIのフィールド名が変わったり、nullableになったり、ネスト構造が変わったりすると、複数画面を修正しなければならない場合があります。DTOとUI model、またはdomain modelを分け、mapperで変換すると保守しやすくなります。TypeScriptを使えば、APIレスポンス型とアプリ内部型を明確に表現できます。データ変換の境界を作ることは、長期的な安定性に大きく貢献します。
10.5 エラー処理を統一する
API通信では、ネットワークエラー、認証エラー、サーバーエラー、バリデーションエラー、タイムアウトなどが発生します。画面ごとにエラー処理をバラバラに書くと、表示や挙動に一貫性がなくなります。API層でエラーを共通形式に変換し、画面側ではユーザー向けメッセージや再試行UIを表示する構成が望ましいです。たとえば、UnauthorizedError、NetworkError、ValidationErrorのように分類すれば、状態管理側で扱いやすくなります。エラー処理の統一は、ユーザー体験と保守性の両方に関係します。
10.6 API層はMockしやすくする
API層をテストしやすくするには、通信実装を直接画面に埋め込まず、serviceやrepositoryを差し替えられる構成にする必要があります。Unit Testでは実際のAPIへ接続せず、mock serverやmock functionを使ってレスポンスを固定します。API層が抽象化されていれば、状態管理や画面のテストでも簡単にFakeを使えます。React Nativeでは、ネットワーク状態や外部サービスに依存したテストは不安定になりやすいため、Mock可能なAPI構成が重要です。テストしやすいAPI層は、開発速度にも貢献します。
11. hooks構成
React Nativeでは、custom hooksを使ってロジックを再利用できます。hooksを適切に整理すると、画面コンポーネントを薄くし、状態管理や副作用処理を分離できます。
11.1 hooksはロジック再利用に使う
custom hookは、複数コンポーネントで使う状態や副作用ロジックをまとめるために使います。たとえば、useAuth、useUserProfile、useDebounce、useKeyboard、useNetworkStatus、usePermissionsなどが考えられます。画面内にuseEffectや状態更新処理が増えすぎた場合、custom hookへ切り出すことで見通しがよくなります。hookはUIを返すものではなく、状態や操作関数を返す形にすると再利用しやすくなります。React Nativeでは端末状態やネイティブイベントを扱うことも多いため、hooksの整理が重要になります。
11.2 feature内hooksと共通hooksを分ける
すべてのhooksを src/hooks に置くと、アプリが大きくなったときに何でも置き場になります。特定機能でしか使わないhookは features/auth/hooks や features/cart/hooks のようにfeature内へ置き、複数機能で使うhookだけを shared/hooks や src/hooks に置くと整理しやすくなります。利用範囲に応じて配置を変えることで、共通hooksが肥大化するのを防げます。hookの置き場所は、そのhookがどの責務を持つかを表す重要な情報になります。
11.3 useEffectの複雑化を防ぐ
React Nativeの画面では、useEffectを使ってAPI取得、イベント購読、権限確認、画面フォーカス時処理などを書くことがあります。useEffectが増えすぎると、処理順序や依存配列が分かりにくくなります。複雑なuseEffectはcustom hookへ切り出し、責務ごとに分けると保守しやすくなります。たとえば、キーボード表示状態はuseKeyboard、画面フォーカス時取得はuseRefreshOnFocusのように分けられます。useEffectは便利ですが、画面に散らばると不具合の原因になりやすいです。
11.4 hooksはテストしやすくする
custom hookは、状態管理や副作用を含むためテスト対象になります。React Native Testing LibraryやReact Hooks Testing Library系の考え方を使い、hookの戻り値や状態更新を確認できます。hookが外部serviceに直接依存している場合、mockしやすいように依存を分離する必要があります。hookをテストできる構成にしておくと、画面全体をレンダリングしなくてもロジックを確認できます。特に認証、フォーム、API取得、端末状態に関わるhookはテスト価値が高いです。
11.5 hooksにUI責務を持たせすぎない
custom hookは便利ですが、UI責務まで持たせすぎると再利用しにくくなります。hookは状態、計算、イベント処理、外部データ取得を担当し、表示そのものはコンポーネントに任せるほうが分かりやすいです。hookが特定画面の文言やstyleまで知っていると、別画面で使いにくくなります。実務では、hookはデータと操作を返し、コンポーネントがそれをどう表示するかを決める構成が望ましいです。責務を分けることで、hookの再利用性とテスト性が高まります。
11.6 hooksの命名規則を統一する
custom hookは use から始める命名が基本です。useAuth、useLoginForm、useUserQuery、useAppThemeのように、何を扱うhookなのかが分かる名前にするべきです。命名が曖昧だと、同じようなhookが複数作られたり、用途が分からなくなったりします。hooksは画面ロジックの中心になりやすいため、命名規則と配置ルールを決めることが重要です。良いhook名は、そのhookの責務を説明するドキュメントのような役割を持ちます。
12. utils・helpers・constants構成
React Nativeプロジェクトでは、共通関数、定数、変換処理をutilsやhelpersに置くことがあります。ただし、これらは何でも置き場になりやすいため、設計に注意が必要です。
12.1 utilsは小さな共通処理を置く場所
utils には、日付変換、文字列整形、数値フォーマット、配列処理、URL生成など、小さな共通処理を置くことがあります。画面ごとに同じ処理を書くと重複が増え、仕様変更時に修正漏れが起こります。utilsに共通化すれば、複数画面で同じ処理を使いやすくなります。ただし、utilsは責務が曖昧になりやすいフォルダです。何でもutilsに入れるのではなく、date、string、number、validationのように用途ごとに分けると保守しやすくなります。
12.2 helpersは具体的用途を持たせる
helpersという名前は便利ですが、意味が広すぎるため乱用に注意が必要です。たとえば、navigationHelper、storageHelper、permissionHelperのように、何を補助するのかが分かる名前にするとよいです。単にhelpersフォルダに大量の関数を置くと、どこに何があるか分からなくなります。実務では、helpersよりもservices、utils、formatters、validatorsのように責務が分かる名前を使うことも多いです。共通処理の置き場所は、名前だけで役割が伝わることが重要です。
12.3 constantsは意味ごとに分ける
constants には、API path、画面名、storage key、色、サイズ、権限名、外部URLなどの定数を置くことがあります。すべてを constants.ts に詰め込むと巨大化するため、routes.ts、storageKeys.ts、apiPaths.ts、links.ts のように分けると管理しやすくなります。定数はアプリ全体で参照されることが多いため、命名と配置が重要です。特にstorage keyやroute nameは変更時の影響が大きいため、文字列を画面に直接書かず、定数化する価値があります。
12.4 formattersは表示変換を担当する
金額、日付、電話番号、住所、ユーザー名などを表示用に変換する処理は、formattersとして分けると便利です。画面ごとにフォーマット処理を書くと、表示形式がばらつきます。formattersを共通化すれば、アプリ全体で一貫した表示ができます。特に多言語対応やタイムゾーン対応がある場合、日付や通貨の表示を統一することは重要です。formatterは小さな関数に見えますが、ユーザーに見える情報の品質に関係します。Unit Testも書きやすいため、共通化の価値が高い領域です。
12.5 validatorsは入力チェックを整理する
フォーム入力チェックは、validatorsとして分けると保守しやすくなります。メールアドレス、パスワード、電話番号、郵便番号、必須入力、文字数制限など、複数画面で同じルールを使うことがあります。画面ごとに入力チェックを書くと、同じルールが微妙に違う形で実装される可能性があります。validatorsを共通化し、テストを書いておけば、仕様変更にも対応しやすくなります。入力チェックはユーザー体験とデータ品質に関わるため、画面から分離して管理するのが望ましいです。
12.6 utils肥大化を定期的に見直す
utils、helpers、constantsは便利ですが、長期運用では肥大化しやすいフォルダです。何でも置ける名前のフォルダは、責務が曖昧なコードの逃げ場になりがちです。定期的に中身を確認し、特定機能にしか使われないものはfeatureへ移し、責務が大きいものはserviceやmoduleへ切り出すことが重要です。共通処理は本当に共通なのか、単に置き場所に困って入れたものなのかを見直すべきです。utilsの整理は、プロジェクト全体の健全性を保つために必要です。
13. assetsとstyles構成
React Nativeでは、画像、フォント、アイコン、色、spacing、typographyなどのUI資産を整理する必要があります。assetsとstylesの構成が整っていると、デザインの一貫性を保ちやすくなります。
13.1 assetsは画像やフォントを管理する
assets フォルダには、画像、アイコン、フォント、アニメーション、JSONなどを置くことがあります。React Nativeでは画像をrequireで読み込んだり、URIで読み込んだりできますが、ローカル画像は整理されたassets構成に置くほうが管理しやすくなります。assets/images、assets/icons、assets/fonts のように用途ごとに分けると、画面実装時に探しやすくなります。デザイン変更や不要画像削除を考えると、assets名と配置ルールは最初に決めておくべきです。assets管理はアプリサイズにも影響します。
13.2 imagesとiconsを分ける
画像とアイコンは用途が違うため、フォルダを分けると分かりやすくなります。imagesにはオンボーディング画像、背景、イラスト、バナーなどを置き、iconsにはタブアイコン、ボタンアイコン、ステータスアイコンなどを置きます。アイコンはSVGライブラリやicon fontで管理することもあります。画像とアイコンが同じ場所に混ざると、数が増えたときに探しにくくなります。UI資産の利用目的をフォルダで表現すると、デザイナーと開発者の連携もスムーズになります。
13.3 stylesは共通デザイン値を管理する
React Nativeでは、StyleSheetを各コンポーネント内に書くことが多いですが、共通の色、文字、spacing、radius、shadowなどはstylesやthemeにまとめると便利です。画面ごとに直接色やサイズを書くと、デザイン変更時に修正箇所が増えます。theme/colors.ts、theme/typography.ts、theme/spacing.ts のように分けると、アプリ全体のデザイン値を管理しやすくなります。共通デザイン値を持つことで、React NativeアプリでもDesign Systemに近い構成を作れます。
13.4 StyleSheetとinline styleを使い分ける
React Nativeでは、StyleSheet.createを使う方法と、inline styleを書く方法があります。固定的なstyleはStyleSheetにまとめると読みやすく、動的なstyleは配列や条件式で組み合わせることがあります。inline styleを多用すると、レンダリングごとに新しいオブジェクトが作られたり、見た目のルールが散らばったりします。一方で、状態に応じて変わるstyleをすべてStyleSheetへ押し込むと条件分岐が複雑になることもあります。実務では、固定値はStyleSheet、共通値はtheme、動的変化は最小限のinlineで扱うと整理しやすくなります。
13.5 Design Systemを構成に入れる
大規模React Nativeアプリでは、Design Systemを独立したフォルダやpackageとして管理することがあります。Button、Text、Input、Card、Modal、Toast、Spacing、Color、Typographyなどを共通化し、画面実装者が同じUIルールを使えるようにします。Design Systemを持つと、UIの一貫性が高まり、デザイン変更にも強くなります。ただし、共通コンポーネントに特定画面の仕様を入れすぎると再利用性が下がります。Design Systemは、アプリ全体の見た目を支える基盤として、機能コードから切り離して管理するのが理想です。
13.6 不要assetsとstyleの重複を削除する
プロジェクトが長く続くと、使われなくなった画像、古いアイコン、重複した色定義、似たようなstyleが増えます。これらはアプリサイズや保守性に影響します。定期的にassetsとstylesを棚卸しし、不要なものを削除することが重要です。特にリデザイン後は、古いUI資産が残りやすくなります。共通styleに移せるもの、画面固有に残すべきものを整理すると、コード全体の見通しが良くなります。UI資産は追加するだけでなく、管理し続ける必要があります。
14. Metro構成
MetroはReact NativeのJavaScriptコードとassetsを処理する重要なビルドツールです。通常は初期設定のまま使えますが、monorepo、alias、SVG、特殊なassets処理では設定を変更することがあります。
14.1 MetroはReact Nativeのバンドラーになる
React Native公式ドキュメントでは、React NativeはJavaScriptコードとassetsをビルドするためにMetroを使用すると説明されています。Metroは、JavaScriptやTypeScriptの依存関係を解決し、アプリで実行できるbundleを生成します。開発中のfast refreshやassets読み込みにも関係します。Metroが正しく設定されていないと、モジュールが見つからない、画像が表示されない、変更が反映されないなどの問題が起こります。React Nativeの実務では、Metroは見えない裏方ではなく、アプリ起動とビルドに関わる重要な構成要素です。
14.2 metro.config.jsで設定を変更する
Metroの設定は、通常 metro.config.js で行います。React Native公式ドキュメントでは、Metro設定はプロジェクトの metro.config.js でカスタマイズでき、React Nativeでは @react-native/metro-config を拡張するべきだと説明されています。 Metro公式ドキュメントでも、Metro設定ファイルとして metro.config.js、TypeScript形式の設定ファイル、JSON設定などが利用できることが説明されています。 設定変更は強力ですが、不要なカスタマイズはアップグレード時の問題につながるため、必要最小限に保つことが大切です。
14.3 SVGや特殊assetsの読み込みに関係する
React NativeでSVGをコンポーネントとして読み込みたい場合、Metroのtransformerやresolver設定を変更することがあります。PNGやJPGだけでなく、SVG、Lottie、カスタムフォント、特殊なJSONなどを扱う場合、Metroのassets処理を理解しておくと問題解決がしやすくなります。設定を誤ると、ファイルが見つからない、型定義が不足する、Androidでは動くがiOSでは動かないといった問題が出ることがあります。assets周りの問題はUIに直接影響するため、Metro設定とTypeScript型定義を合わせて管理することが重要です。
14.4 monorepoではwatchFoldersが重要になる
monorepo構成でReact Nativeアプリを運用する場合、アプリ外のpackagesを参照することがあります。このときMetroが外部フォルダの変更を監視できるように、watchFoldersやresolver設定を調整することがあります。Metro設定がmonorepoに対応していないと、内部packageの変更が反映されない、依存解決に失敗する、重複Reactが読み込まれるといった問題が起こります。monorepoはコード共有に便利ですが、Metro、package manager、TypeScript path aliasの整合性を取る必要があります。構成が複雑になるため、ドキュメント化が重要です。
14.5 cache問題への対応を理解する
React Native開発では、Metro cacheが原因で変更が反映されない、古いbundleが使われる、解決済みモジュールが残ることがあります。開発中に不可解なエラーが出た場合、Metro cacheをクリアして再起動することで解決することがあります。ただし、cache削除を毎回の対処にするのではなく、根本原因も確認するべきです。依存関係の重複、alias設定、ファイル名の大文字小文字違い、watchFolders不足などが原因の場合もあります。Metro cacheの理解は、開発時のトラブルシューティングに役立ちます。
14.6 Metro設定はアップグレード時に確認する
React Native本体をアップグレードすると、Metro設定の推奨形や内部挙動が変わることがあります。カスタム設定を多く入れているプロジェクトほど、アップグレード時に問題が出やすくなります。実務では、React Nativeアップグレード時にmetro.config.jsを公式テンプレートと比較し、不要な設定や古い設定を削除することが重要です。Metro設定は一度書いたら終わりではなく、React Nativeのバージョンに合わせて見直す必要があります。安定した構成を保つには、カスタマイズを最小限にする方針が有効です。
15. Babel構成
Babelは、React NativeでJavaScriptやTypeScriptを実行可能な形へ変換する重要な仕組みです。通常はテンプレート設定で動きますが、alias、Reanimated、特殊な構文を使う場合に設定変更が必要になることがあります。
15.1 babel.config.jsは変換設定を管理する
React Nativeプロジェクトでは、babel.config.js がJavaScriptやTypeScriptの変換設定を管理します。React Native用のプリセットを使い、JSXやTypeScript構文をReact Nativeが実行できる形へ変換します。通常は初期設定のままで問題ありませんが、ライブラリによってBabel pluginの追加が必要になることがあります。設定を誤ると、アプリが起動しない、特定ライブラリが動かない、ビルド時に構文エラーが出るといった問題が起こります。Babel設定は地味ですが、React Nativeアプリの実行に深く関わります。
15.2 path aliasにBabel設定が関係する
@/components や @/screens のようなpath aliasを使う場合、TypeScriptのpaths設定だけでなく、Babel側のmodule-resolver設定が必要になることがあります。tsconfigだけ設定しても、実行時にMetroやBabelが解決できなければエラーになります。実務では、tsconfig、babel.config.js、metro.config.js、eslint設定のaliasを揃える必要があります。設定がずれると、エディタ上では解決できるのに実行時に失敗する、またはその逆の問題が起こります。aliasは便利ですが、複数ツールの整合性が重要です。
15.3 Reanimatedなど一部ライブラリで設定が必要になる
React Native Reanimatedなど、一部のライブラリではBabel pluginの設定が必要になることがあります。設定順序が重要な場合もあり、公式手順どおりに追加しないとランタイムエラーが出る可能性があります。React NativeライブラリはJavaScriptだけでなくネイティブやBabel変換に依存するものもあるため、導入時にはドキュメントを確認する必要があります。packageを追加しただけでは完了しない場合があります。Babel設定の変更は、ライブラリ導入の重要な一部として扱うべきです。
15.4 BabelとTypeScriptの役割を分ける
React Nativeでは、TypeScriptの型チェックとBabelによる変換の役割を分けて考える必要があります。TypeScriptは型を確認し、Babelはコードを実行可能な形へ変換する役割を持ちます。型チェックをCIで行うには、tsc --noEmit のようなコマンドをscriptsに入れることがあります。Babelが変換できるからといって、型が正しいとは限りません。実務では、Babel設定、tsconfig、typecheckコマンドを組み合わせて、実行と型安全性の両方を確保します。
15.5 Babel設定は複雑にしすぎない
Babelは強力ですが、pluginを増やしすぎるとビルドやデバッグが複雑になります。どのpluginが何をしているか分からない状態になると、アップグレード時に問題が起きやすくなります。Babel設定を変更する場合は、導入理由を明確にし、コメントやドキュメントに残すとよいです。不要になったpluginは削除し、公式テンプレートとの差分を定期的に見直すことも重要です。Babel設定は少ないほど安全というわけではありませんが、必要最小限に保つのが実務では扱いやすいです。
15.6 Babel問題はMetroやcacheとも関係する
Babel設定を変更した後、Metro cacheが残っていると、変更がすぐに反映されない場合があります。ライブラリ導入後にエラーが残る場合、Babel設定、Metro設定、cache、node_modules、lockfileを合わせて確認する必要があります。React Nativeのビルド問題は、1つのファイルだけでなく複数ツールの組み合わせで起こることが多いです。BabelはMetroと連携して動くため、問題切り分けでは両方を見る必要があります。設定ファイル同士の関係を理解すると、トラブル対応が速くなります。
16. androidフォルダの構成
android フォルダは、React NativeアプリのAndroid側ネイティブプロジェクトです。Gradle、Manifest、MainActivity、MainApplication、署名、flavorsなどが含まれます。
16.1 androidフォルダはAndroidネイティブの基盤になる
React Nativeプロジェクトの android フォルダには、Android Studioで開けるネイティブプロジェクトが含まれます。JavaScriptで画面を作っていても、Androidアプリとしてビルド・署名・配布するにはこのフォルダの設定が必要です。Gradle、AndroidManifest、KotlinまたはJavaコード、リソース、署名設定などが関係します。ライブラリによってはAndroid側に設定を追加する必要があります。React Native開発では、JavaScriptだけでなくAndroidフォルダの基本も理解しておくと、ビルドエラーや端末固有問題に対応しやすくなります。
16.2 GradleはAndroidビルドを管理する
Android側のビルドはGradleで管理されます。React Native本体、Android Gradle Plugin、Kotlin、依存ライブラリ、minSdk、targetSdk、署名、flavorsなどが関係します。React Nativeのライブラリを追加した後、Androidビルドだけ失敗する場合、Gradle設定やネイティブ依存が原因のことがあります。特にReact Native本体をアップグレードすると、Android側テンプレートやGradle設定の変更が必要になることがあります。Androidビルド問題を解決するには、Gradleファイルを読めることが重要です。
16.3 AndroidManifest.xmlは権限やintentを管理する
AndroidManifest.xmlには、アプリが必要とする権限、Activity、intent filter、Deep Link、FileProviderなどが定義されます。カメラ、位置情報、通知、Bluetooth、インターネットなどの権限が必要なライブラリを使う場合、Manifest設定を確認する必要があります。Manifestに不要な権限が残ると、ユーザーの信頼やストア審査にも影響します。Deep Linkを使う場合は、intent filter設定も重要です。React Nativeアプリでも、AndroidManifestはセキュリティと機能に関わる重要ファイルとして扱う必要があります。
16.4 MainActivityはReact NativeのAndroid入口になる
MainActivityは、Android側でReact Nativeアプリを起動する入口のActivityです。通常はテンプレートに沿って構成されますが、Splash Screen、Deep Link、画面向き、ネイティブSDK連携などで編集することがあります。MainActivityに多くの処理を入れすぎると、Android側が複雑になります。できるだけテンプレートに近い状態を保ち、必要な処理は専用のネイティブモジュールやクラスへ分けると保守しやすくなります。MainActivityはReact NativeとAndroidネイティブの接点として重要です。
16.5 MainApplicationはパッケージ登録に関係する
MainApplicationは、React NativeアプリのAndroid側アプリケーションクラスです。ネイティブモジュールやパッケージ登録、New Architecture関連設定などに関係する場合があります。Auto-linkingにより多くのライブラリは自動登録されますが、特殊なネイティブモジュールでは手動設定が必要になることもあります。React Nativeアップグレード時には、MainApplicationのテンプレート差分も確認する必要があります。Android側のアプリ起動やネイティブ連携に関わるため、変更時には慎重にレビューするべきです。
16.6 Android側は実機確認が重要になる
Android側の設定は、エミュレーターでは動いても実機や特定メーカー端末で問題が出ることがあります。通知、権限、バックグラウンド動作、Deep Link、ファイルアクセス、カメラなどは端末差が出やすい領域です。React NativeのJavaScriptコードが正しくても、Android ManifestやGradle、ネイティブSDK設定が原因で不具合が起こる場合があります。Android側変更を行ったら、最低限主要なAndroidバージョンや実機で確認することが重要です。クロスプラットフォーム開発でも、Android固有の品質確認は必要です。
17. iosフォルダの構成
ios フォルダは、React NativeアプリのiOS側ネイティブプロジェクトです。Xcode、Podfile、Info.plist、AppDelegate、署名、Capabilitiesなどが関係します。
17.1 iosフォルダはXcodeプロジェクトを含む
React Nativeプロジェクトの ios フォルダには、Xcodeで開くiOSプロジェクトが含まれます。JavaScriptでUIを作っていても、iOSアプリとしてビルド、署名、配布するにはXcode側の設定が必要です。Info.plist、Podfile、AppDelegate、Bundle Identifier、Signing、Capabilities、Push通知、Associated Domainsなどが関係します。React NativeアプリでiOSだけビルドに失敗する場合、JavaScriptではなくXcodeやCocoaPodsの設定が原因のことがあります。iOSフォルダの基本理解は実務で欠かせません。
17.2 PodfileはiOS依存関係を管理する
React NativeのiOS側では、CocoaPodsを使ってネイティブ依存関係を管理することが多いです。ios/Podfile には、React Native本体やネイティブライブラリのPods設定が含まれます。JavaScriptライブラリを追加しただけでも、iOS側ではpod installが必要になる場合があります。PodsのバージョンやXcode設定が合わないと、iOSビルドだけ失敗することがあります。React Native開発では、package managerの依存関係とCocoaPodsの依存関係が連動していることを理解する必要があります。
17.3 Info.plistは権限説明やURL Schemeを管理する
iOSのInfo.plistには、権限説明、URL Scheme、画面向き、バックグラウンドモード、外部連携設定などが含まれます。カメラ、写真、位置情報、マイクなどを使う場合、ユーザーに表示する利用目的説明を設定する必要があります。設定が不足していると、アプリがクラッシュしたり、審査で問題になることがあります。Deep Linkや外部認証を使う場合もInfo.plistが関係します。React Nativeアプリでも、iOSのInfo.plistはユーザー体験、プライバシー、外部連携に関わる重要ファイルです。
17.4 AppDelegateはiOS起動処理に関わる
AppDelegateは、iOSアプリの起動や外部イベント処理に関わる重要なクラスです。React Nativeアプリでは、React NativeブリッジやルートViewの初期化、Push通知、Deep Link、Firebase、外部SDK初期化などでAppDelegateを編集することがあります。AppDelegateに多くの処理を詰め込むと、iOS側の起動処理が読みにくくなります。必要な初期化は専用クラスへ分け、AppDelegateは入口として薄く保つと保守しやすくなります。iOS側の起動問題を調査するときにも重要な場所です。
17.5 SigningとCapabilitiesは配布に直結する
iOSアプリでは、SigningとCapabilitiesが正しく設定されていないと、実機ビルド、TestFlight配布、App Store提出ができません。Push Notifications、Associated Domains、Sign in with Apple、Keychain Sharing、App Groupsなどを使う場合、Capabilities設定が必要になります。React NativeのJavaScriptコードが正しくても、Xcode側の署名やCapabilitiesが間違っていれば機能は動きません。iOS配布はネイティブ設定への依存が大きいため、React Native開発でもXcode設定の理解が重要です。
17.6 iOS側はXcodeとCIの両方で確認する
iOS側設定は、ローカルXcodeでは動くがCIで失敗する、またはその逆のことがあります。証明書、Provisioning Profile、CocoaPods、Xcodeバージョン、Ruby環境などが影響します。CI/CDでiOSビルドを安定させるには、ローカル設定に依存しない構成が必要です。React NativeのiOSフォルダは、開発時だけでなくリリース時にも重要です。iOS側変更を行ったら、Xcode上の確認だけでなく、CIでのビルド確認も行うと安全です。
18. Native Modules構成
React Nativeでは、JavaScriptからネイティブ機能を呼び出すためにNative Modulesを使うことがあります。既存ライブラリで足りない機能や社内SDK連携では、独自Native Moduleが必要になる場合があります。
18.1 Native ModulesはJavaScriptとネイティブをつなぐ
React Native公式ドキュメントでは、Native ModuleシステムはJava、Objective-C、C++などのネイティブクラスをJavaScriptから利用できるようにする仕組みだと説明されています。これにより、JavaScriptだけでは扱えないOS固有機能やネイティブSDKをReact Nativeアプリから利用できます。たとえば、独自デバイス連携、社内認証SDK、特殊なカメラ処理、決済SDKなどでNative Moduleが必要になることがあります。Native Modulesは強力ですが、JavaScript層とネイティブ層の境界を増やすため、設計とテストが重要になります。
18.2 Turbo Native Modulesでは仕様定義が重要になる
React Native公式ドキュメントでは、Turbo Native ModuleはTypeScriptまたはFlowで仕様を宣言し、CodegenがAndroidとiOS向けのコードを生成する仕組みを持つと説明されています。仕様には、JavaScriptとネイティブコードの間でやり取りするメソッドやデータ型を定義します。これは従来より型安全で一貫したNative Module設計に役立ちます。Native Moduleを作る場合、まずJavaScript側から見たAPIを明確にし、その仕様に合わせてAndroidとiOS実装を作ることが重要です。境界の型が曖昧だと、ランタイムエラーやプラットフォーム差分が増えます。
18.3 Android Native ModuleはKotlinやJavaで実装する
Android向けNative Moduleは、KotlinまたはJavaで実装します。React Native公式ドキュメントでも、AndroidのNative Module作成例として、JavaScriptからAndroidのCalendar APIへアクセスするモジュールを作る手順が紹介されています。 実務では、Android SDK、社内SDK、端末固有機能、バックグラウンド処理などをNative Moduleとして公開することがあります。Android実装では、ActivityやContext、Permission、Thread、Lifecycleの扱いに注意が必要です。JavaScript側から簡単に呼べても、ネイティブ側ではAndroidの制約を理解して設計する必要があります。
18.4 iOS Native ModuleはSwiftやObjective-Cで実装する
iOS向けNative Moduleは、SwiftまたはObjective-Cで実装します。React Native公式ドキュメントでも、iOSのNative Module作成例として、JavaScriptからAppleのCalendar APIへアクセスするモジュールを作る手順が紹介されています。 iOS実装では、権限、Main Thread、AppDelegate、Info.plist、Delegate callback、SwiftとObjective-Cの橋渡しなどに注意が必要です。Androidで動くNative ModuleがiOSで同じように動くとは限りません。React NativeのNative Moduleは、両プラットフォームで同じJavaScript APIを提供しつつ、内部実装はOSごとに適切に設計する必要があります。
18.5 Native ModuleはAPIを薄く保つ
Native Moduleに多くのビジネスロジックを入れると、AndroidとiOSで同じ処理を二重実装することになり、保守が難しくなります。基本方針として、OS固有機能やネイティブSDK呼び出しだけをNative Moduleに置き、共通ロジックはJavaScriptまたはTypeScript側に置くほうが保守しやすいです。Native ModuleはJavaScriptから見た薄い境界として設計し、入力と出力を明確にするべきです。エラー形式も統一し、AndroidとiOSで差が出ないようにします。境界が薄いほど、テストと保守が楽になります。
18.6 Native Moduleはドキュメント化する
Native Moduleは、JavaScript、Android、iOSの3つの領域にまたがるため、ドキュメントが非常に重要です。どのメソッドがあり、どの引数を受け取り、どの値を返し、どのエラーが起こり、どの権限や設定が必要なのかを記録するべきです。Native Moduleの仕様が曖昧だと、JavaScript側とネイティブ側で認識がずれます。特に社内SDKや独自機能では、後から担当者が変わったときに困りやすいです。Native Moduleは実装だけでなく、仕様管理もプロジェクト構成の一部として扱う必要があります。
19. testing構成
React Nativeプロジェクトでは、Unit Test、Component Test、Integration Test、E2E Testを役割に応じて使い分ける必要があります。テスト構成が整っていると、リファクタリングやアップグレードを安全に進めやすくなります。
19.1 JestはReact Nativeテストの基本になる
React Native公式のTesting概要では、Jestの describe を使って関連するテストをグループ化できることなど、React Nativeテストの基本が説明されています。Jestは、Unit Testやコンポーネントテストの中心的なツールとしてよく使われます。関数、hooks、reducers、formatters、validators、servicesなどを高速にテストできます。React Nativeアプリではネイティブ依存があるため、必要に応じてmockを用意することも重要です。JestをCIに組み込むことで、基本的な品質確認を継続できます。
19.2 React Native Testing LibraryでUIを確認する
React Native Testing Libraryを使うと、コンポーネントの表示やユーザー操作をテストできます。Buttonが表示されるか、入力後にエラーが出るか、loading状態でIndicatorが出るかなど、ユーザー視点に近い確認ができます。実装詳細ではなく、表示されるテキストやアクセシビリティラベルを基準にテストすると、壊れにくいテストになります。画面全体をE2Eで確認するより軽いため、共通コンポーネントや重要画面の状態確認に向いています。UIテストを構成に入れることで、変更時の安心感が増します。
19.3 hooksとstoreをUnit Testする
custom hooks、store、reducers、selectors、state actionsはUnit Testの対象になります。画面全体をレンダリングしなくても、状態更新やロジックを確認できます。たとえば、ログイン成功時にauth stateが更新される、API失敗時にerror stateになる、selectorが正しい値を返すといった確認ができます。状態管理を画面から分離しておくほど、Unit Testが書きやすくなります。React Nativeプロジェクトでは、UIテストだけでなく、状態管理ロジックのテストも重要です。
19.4 API層はmockしてテストする
API通信を含む処理をテストする場合、実際のサーバーへ接続するのではなく、mockやfakeを使うのが一般的です。これにより、ネットワーク状態や外部サービスに依存しない安定したテストが書けます。API clientやserviceを直接画面に埋め込まず、差し替え可能な構成にしておくとテストが楽になります。レスポンス成功、認証エラー、ネットワークエラー、バリデーションエラーなどを固定データで確認できます。API層のmockしやすさは、プロジェクト構成の良し悪しを表します。
19.5 E2E Testは重要フローに絞る
React Nativeアプリでは、Detoxなどを使ってE2E Testを行うことがあります。ログイン、登録、購入、検索、プロフィール更新など、アプリ全体の重要なユーザーフローを実機またはシミュレーターに近い環境で確認できます。ただし、E2E Testは実行が重く、不安定になりやすいため、すべての画面を網羅するのではなく重要フローに絞るのが現実的です。Unit Test、Component Test、E2E Testの役割を分けることで、品質と実行速度のバランスを取れます。E2Eは少数でも信頼性が高いものを維持することが重要です。
19.6 テストはCIで継続実行する
テストはローカルで書くだけでなく、CIで継続実行することで価値が高まります。Pull RequestごとにJest、typecheck、lintを実行し、重要なE2E Testはリリース前や定期実行にする構成がよく使われます。テストがCIに組み込まれていないと、開発者が実行し忘れる可能性があります。React NativeはJavaScript層とネイティブ層があるため、テスト対象も複数に分かれます。自動化されたテスト構成を持つことで、アップグレードやリファクタリングを安全に進めやすくなります。
20. React Nativeプロジェクト構成のまとめ
React Nativeプロジェクト構成は、JavaScript・TypeScriptのReactコード、AndroidとiOSのネイティブ設定、Metro、Babel、package.json、テスト、CI/CDが組み合わさったものです。構成を理解することで、開発速度、保守性、品質が大きく向上します。
20.1 初心者はsrcとpackage.jsonから理解する
React Native初心者が最初に理解すべきなのは、src と package.json です。src はアプリ本体のコードを整理する場所であり、package.json は依存関係と開発コマンドを管理します。次に、android と ios の役割を理解すると、ビルドエラーやネイティブ機能の問題に対応しやすくなります。最初からNative Modulesやmonorepoまで理解する必要はありませんが、React NativeはJavaScriptだけではないという意識を持つことが重要です。基本構成を理解すれば、エラー対応や機能追加がかなり楽になります。
20.2 小規模アプリはシンプルでよい
小規模React Nativeアプリでは、screens、components、navigation、services、hooks、utils 程度のシンプルな構成で十分です。最初から複雑なfeatures構成やmonorepoを導入すると、開発速度が落ちる場合があります。ただし、シンプルであることと無秩序であることは違います。画面にAPI通信を直接書く、コンポーネントに状態管理を詰め込む、型定義を省略する構成は後から保守しにくくなります。小規模でも最低限の責務分離を意識するべきです。
20.3 中規模アプリはfeatures構成を検討する
画面数や機能数が増えてきたら、features構成を検討するとよいです。auth、home、profile、cart、settingsのように機能ごとにコードをまとめることで、関連ファイルを探しやすくなります。共通UIはshared componentsへ、共通処理はcoreやutilsへ、機能固有UIはfeature内componentsへ置くと整理しやすくなります。中規模アプリでは、navigation型、API型、状態管理、テストも重要になります。早い段階で構成ルールを整えると、大規模化したときの負担を減らせます。
20.4 大規模アプリはmonorepoやmodule分割を考える
大規模React Nativeアプリでは、monorepoや内部packageを使ってDesign System、API client、shared types、feature modulesを分けることがあります。これにより、複数アプリや複数チームでコードを共有しやすくなります。ただし、monorepoではMetro、TypeScript alias、package manager、CIの設定が複雑になります。単にpackagesを増やすだけではなく、依存方向、公開API、ビルド、テストを設計する必要があります。大規模構成では、コード整理だけでなく運用ルールも重要になります。
20.5 よくある失敗を避ける
React Nativeプロジェクトでよくある失敗は、App.tsxにすべてを書く、screenが肥大化する、componentsが何でも置き場になる、navigation型がない、API通信が画面に直書きされる、androidやios設定を理解せず変更する、MetroやBabelの設定が複雑すぎる、テストがない、というものです。これらは最初は小さな問題に見えますが、機能追加やアップグレード時に大きな負担になります。React Nativeは開発速度が速い一方、構成を雑にしても最初は動いてしまうため、早めに整理することが重要です。
20.6 理想は変更しやすい構成
理想的なReact Nativeプロジェクト構成は、階層が多いだけの構成ではなく、変更しやすい構成です。新しい画面を追加しやすく、API変更に対応しやすく、状態管理を追いやすく、AndroidとiOSの問題を切り分けやすく、テストとCI/CDを回しやすいことが重要です。小規模ではシンプルに、中規模ではfeaturesとsharedを意識し、大規模ではmonorepoや内部packageを検討する流れが現実的です。React Nativeプロジェクト構成は、単なるフォルダ整理ではなく、アプリの品質と開発速度を支える設計そのものです。
おわりに
React Nativeプロジェクト構成を理解すると、src、components、screens、navigation、services、hooks、package.json、metro.config.js、babel.config.js、android、ios の役割が明確になります。React NativeはReactの考え方でネイティブアプリを作れる強力なフレームワークですが、実務ではJavaScriptやTypeScriptだけでなく、AndroidとiOSのネイティブ設定、Metro、Babel、Native Modules、テスト、CI/CDまで含めて理解する必要があります。構成を理解していれば、問題がJavaScript側にあるのか、Android側にあるのか、iOS側にあるのかを切り分けやすくなります。
良いReact Nativeプロジェクト構成は、最初から大規模で複雑なものにする必要はありません。小規模では screens、components、navigation、services のようなシンプルな構成で始め、アプリが成長したら features、shared、core、store、types のように責務を分けるのが現実的です。さらに大きくなったら、Design SystemやAPI clientを内部package化し、monorepoやCI/CDを整備することで、長期的に保守しやすい状態を作れます。
React Native開発では、画面を速く作る力だけでなく、構成を設計する力が長期的な品質を左右します。TypeScriptで型を整理し、navigationとAPI層を明確にし、componentsとfeaturesを分け、androidとiosの設定を理解し、MetroとBabelを必要最小限に管理し、テストとCI/CDを組み込むことで、変更に強く成長しやすいReact Nativeアプリを作ることができます。
EN
JP
KR