🚁

React Native デザインパターン

に公開
2

はじめに

弊社ではモバイルアプリ開発のフレームワークをFlutterからReact Nativeへ移行しました。
理由としては、

  • AIとの相性が良い
    React Nativeだけでなく、WebのReactのコードもLLMに多く学習されているため、質の高いコードをAIが生成しやすく、バイブコーディングが非常に効率的になります。
  • OSに依存したUIで、よりネイティブに近い見た目と操作感が得られる
    React NativeはiOSやAndroidのネイティブUIコンポーネントを使って表示する(React Nativeブリッジ)ため、違和感のない自然なUIが実現できます。
    FlutterではOSに関わらず実装したUIがそのまま表示されますが、自由度が高い反面、ゲームなどの凝ったUIでなければ、ネイティブのコンポーネントを用いた方が綺麗なUIができると感じました(特にiOS)。
  • Webでも動作確認しやすい
    同じコードでWebでも確認でき、多くの人に検証してもらう際に、webにデプロイしたもので検証が行えるため、開発効率が上がります。
  • Expoによる簡易な動作確認が可能
    QRコード一つでスマホ実機での確認ができ、開発効率が向上します。
  • Web開発者の参入がしやすい
    Reactとの共通点が多く、学習コストが低いため、既存のWebエンジニアもスムーズに参加できます。

この移行をきっかけに、開発の標準化のために、React Nativeにおけるデザインパターンを整理してみようと思います。

React Nativeの歴史と進化

  • 2012年
    ・Jordan WalkeがFacebookの広告管理アプリの効率化のためにReactを開発。
    ・Mark Zuckerbergがモバイルファースト戦略を発表、HTMLの限界を強調。
    ・Christopher ChedeauがFacebookに加入、JavaScriptからネイティブUIコンポーネントの基盤構築を開始。
  • 2013年
    ・Pete HuntによりReactがFacebookの内部スタックから分離、オープンソース化。
    ・内部ハッカソンでReact Nativeのプロトタイプが開発。
    ・JavaScriptを使用したネイティブモバイルアプリ構築のアイデアが誕生。
  • 2015年
    ・1月にReact.JSカンファレンスでReact Nativeの基本フレームワークを公開。
    ・3月にGitHubでオープンソース化、開発者コミュニティの貢献が可能に。
    ・9月にReact Native for Androidをリリース、iOSに加えてAndroidサポートを開始。
    ・F8カンファレンスでGitHub公開、オープンソースプロジェクトとしての地位を確立。
  • 2016年
    ・F8カンファレンスでMicrosoftとSamsungがWindowsおよびTizenへの拡張を発表。
    ・クロスプラットフォームの範囲が拡大。
  • 2017年
    ・一部React NativeアプリがApple App Storeで拒否される問題が発生。
    ・4月までに問題解決、拒否はReact Native自体ではなく特定ライブラリに起因。
  • 2018年-2023年
    ・コミュニティの貢献によりライブラリとツールが拡充、安定性が向上。
    ・GitHub統計:1002人のコントリビューター、7,971コミット。
    ・Google TrendsでiOS/Android開発を上回る人気。
    ・2週間ごとのリリース候補ブランチを採用、継続的な改善を実施。
  • 2024年
    ・10月にReact Native 0.76リリース、新アーキテクチャがデフォルトに。
    ・React 18の機能(Suspense、Transitions、自動バッチング、useLayoutEffect)を完全サポート。
    ・新しいネイティブモジュールとコンポーネントシステムを導入、ブリッジなしで型安全なコードを実現。
    ・850以上のライブラリが互換性対応、週200,000ダウンロード以上のライブラリが対応。
    ・Meta(Facebook、Instagram Questアプリ)、Expensify、Kraken、Blueskyで本番採用。
  • 2025年
    ・1月21日にReact Native 0.77リリース、スタイリング機能(display: contents、boxSizing、mixBlendMode、outline)を追加。
    ・Androidの16KBページサイズ制限対応、Swiftコミュニティテンプレートを導入。
    ・2月19日にReact Native 0.78リリース、React 19統合、Android Vector drawablesサポート、iOSブラウンフィールド統合を改善。
    ・4月8日にReact Native 0.79リリース、Metroスタートアップ時間高速化、JSバンドル圧縮によるAndroid起動時間改善。

ディレクトリ構成と設計原則

ポイント

  • React Native プロジェクトのディレクトリ構成には「最適な」方法はなく、プロジェクトの規模やニーズに応じて選択しますが、経験に基づく実践的な構造が推奨されます。
  • 設計原則は、モジュール性、テスト性、パフォーマンス最適化、将来性を重視する必要があります。

推奨ディレクトリ構成

nakapooooon さんの例

https://qiita.com/nakapon9517/items/6b99adc29e4597ed47e1

React Native プロジェクトのディレクトリ構成として、以下のような構造が実践的で拡張性が高いとされています。

root/
 L src
   ├ assets     // アイコンなど画像系
   ├ atoms      // ZustandやJotaiなど状態管理系
   ├ components // 画面内部品
   ├ constants  // 定数
   ├ dao        // データアクセス関連
   ├ hooks      // Hooks関連
   ├ localize   // 多言語対応関連
   ├ mocks      // テストやデバッグに使うモックデータ
   ├ navigation // 画面遷移関連(expo-routerはv1の頃から様子見)
   ├ purchases  // 課金関連
   ├ screens    // 画面
   ├ types      // 型など
   ├ utils      // その他横断的に利用する処理など
 ├ package.json
 └ app.json

CodingEasyPeasy の提案:

https://www.codingeasypeasy.com/blog/setting-up-a-react-native-project-in-2025-a-comprehensive-guide

Expo を使用する場合のディレクトリ構成。シンプルでモダンなプロジェクトに適しています。
Expo ベースと React Native CLI ベースの基本構造を提供。

記事では、src/ 以下の詳細なサブフォルダ構成は明示されていませんが、React Native プロジェクトの標準的なプラクティス(components/、screens/、navigation/ など)を補完して構築しました。

root/
├── .expo/                    # Expoの設定やキャッシュ
├── src/
│   ├── assets/               # 画像、アイコン、フォントなどのリソース
│   │   ├── images/
│   │   └── fonts/
│   ├── components/           # 再利用可能なUIコンポーネント
│   │   ├── Button.tsx
│   │   └── Header.tsx
│   ├── constants/            # 定数(APIエンドポイント、テーマなど)
│   │   ├── api.ts
│   │   └── theme.ts
│   ├── hooks/                # カスタムフック
│   │   ├── useAuth.ts
│   │   └── useTheme.ts
│   ├── navigation/           # ナビゲーション設定(React Navigationなど)
│   │   ├── AppNavigator.tsx
│   │   └── types.ts
│   ├── screens/              # 各画面コンポーネント
│   │   ├── HomeScreen.tsx
│   │   └── ProfileScreen.tsx
│   ├── types/                # TypeScriptの型定義
│   │   ├── user.ts
│   │   └── navigation.ts
│   ├── utils/                # ユーティリティ関数
│   │   ├── formatDate.ts
│   │   └── apiClient.ts
│   └── App.tsx               # アプリケーションのエントリーポイント
├── app.json                  # Expoの設定ファイル
├── package.json              # 依存関係とスクリプト
├── tsconfig.json             # TypeScript設定
├── babel.config.js           # Babel設定
└── metro.config.js           # Metroバンドラーの設定

Waldo Blog の分類:

https://www.waldo.com/blog/react-native-project-structure

以下の 3 つのアプローチを提案:

・いずれの構造も、React Native 特有の要件(例: assets/ での画像管理、navigation/ での React Navigation 設定)を満たし、TypeScript や Expo、React Native CLI に対応しています。
・実際のプロジェクトでは、これらを組み合わせてカスタマイズすることが一般的です(例: 型ベース+一部機能ベース)。

1. 型ベース(Type-Based)

ファイルやフォルダを種類(コンポーネント、フック、定数など)で分類する構造。シンプルで小~中規模プロジェクトに適しています。

root/
├── src/
│   ├── assets/               # 画像、アイコン、フォントなどのリソース
│   │   ├── images/
│   │   └── fonts/
│   ├── components/           # 再利用可能なUIコンポーネント
│   │   ├── Button.tsx
│   │   └── Header.tsx
│   ├── constants/            # 定数(例: APIエンドポイント、テーマカラー)
│   │   ├── api.ts
│   │   └── theme.ts
│   ├── hooks/                # カスタムフック
│   │   ├── useAuth.ts
│   │   └── useTheme.ts
│   ├── navigation/           # ナビゲーション設定(React Navigationなど)
│   │   ├── AppNavigator.tsx
│   │   └── types.ts
│   ├── screens/              # 各画面コンポーネント
│   │   ├── HomeScreen.tsx
│   │   └── ProfileScreen.tsx
│   ├── types/                # TypeScriptの型定義
│   │   ├── user.ts
│   │   └── navigation.ts
│   ├── utils/                # ユーティリティ関数
│   │   ├── formatDate.ts
│   │   └── apiClient.ts
│   └── App.tsx               # アプリケーションのエントリーポイント
├── package.json
├── app.json                  # ExpoまたはReact Nativeの設定
├── tsconfig.json             # TypeScript設定
└── metro.config.js           # Metroバンドラーの設定

2. 機能ベース(Feature-Based)

機能やモジュールごとにフォルダを分け、その中に型ベースの分類を配置する構造。迅速なイテレーションが必要なプロジェクトやスタートアップに適しています。

root/
├── src/
│   ├── features/             # 機能ごとのフォルダ
│   │   ├── auth/             # 認証関連
│   │   │   ├── components/
│   │   │   │   ├── LoginForm.tsx
│   │   │   │   └── SignupForm.tsx
│   │   │   ├── screens/
│   │   │   │   ├── LoginScreen.tsx
│   │   │   │   └── SignupScreen.tsx
│   │   │   ├── hooks/
│   │   │   │   └── useAuth.ts
│   │   │   └── types/
│   │   │       └── auth.ts
│   │   ├── profile/          # プロフィール関連
│   │   │   ├── components/
│   │   │   │   └── ProfileCard.tsx
│   │   │   ├── screens/
│   │   │   │   └── ProfileScreen.tsx
│   │   │   └── types/
│   │   │       └── profile.ts
│   ├── assets/               # 画像、フォントなどのリソース
│   │   ├── images/
│   │   └── fonts/
│   ├── constants/            # グローバル定数
│   │   ├── api.ts
│   │   └── theme.ts
│   ├── navigation/           # ナビゲーション設定
│   │   ├── AppNavigator.tsx
│   │   └── types.ts
│   ├── utils/                # 共通ユーティリティ
│   │   ├── formatDate.ts
│   │   └── apiClient.ts
│   └── App.tsx               # アプリケーションのエントリーポイント
├── package.json
├── app.json
├── tsconfig.json
└── metro.config.js

3. アトム・分子・有機体(Atomic Design)

UI を最小単位(アトム)、組み合わせ(分子)、ページ(有機体)に分割する構造。大規模で複雑な UI を持つプロジェクトに適しています。

root/
├── src/
│   ├── atoms/                # 最小単位のUIコンポーネント(ボタン、テキストなど)
│   │   ├── Button.tsx
│   │   ├── TextInput.tsx
│   │   └── Icon.tsx
│   ├── molecules/            # アトムを組み合わせたコンポーネント
│   │   ├── FormField.tsx   # テキスト入力+ラベル
│   │   └── Card.tsx        # アイコン+テキスト
│   ├── organisms/            # 分子を組み合わせた複雑なコンポーネント
│   │   ├── LoginForm.tsx   # フォームフィールド+ボタン
│   │   └── ProfileHeader.tsx
│   ├── screens/              # 画面(有機体を組み合わせて構成)
│   │   ├── HomeScreen.tsx
│   │   └── ProfileScreen.tsx
│   ├── assets/               # 画像、フォントなどのリソース
│   │   ├── images/
│   │   └── fonts/
│   ├── constants/            # 定数
│   │   ├── api.ts
│   │   └── theme.ts
│   ├── hooks/                # カスタムフック
│   │   ├── useAuth.ts
│   │   └── useTheme.ts
│   ├── navigation/           # ナビゲーション設定
│   │   ├── AppNavigator.tsx
│   │   └── types.ts
│   ├── types/                # TypeScriptの型定義
│   │   ├── user.ts
│   │   └── navigation.ts
│   ├── utils/                # ユーティリティ関数
│   │   ├── formatDate.ts
│   │   └── apiClient.ts
│   └── App.tsx               # アプリケーションのエントリーポイント
├── package.json
├── app.json
├── tsconfig.json
└── metro.config.js

Hooks の設計パターン

React と変わらないので割愛します。
以下の記事が参考になりました。

https://zenn.dev/grooves/articles/a1d268ac45ed67#react-hooks-デザインパターン

ルーティングパターン

ルーティングは React Navigation

https://www.peanutsquare.com/your-complete-guide-to-react-native-navigation-in-2025/

React Navigation は、React Native 専用に設計されており、スタックナビゲーション、タブナビゲーション、ドロワーナビゲーションなど、モバイルアプリに必要なナビゲーション機能を幅広く提供します。2025 年時点でも、最新の記事やドキュメントでその利用が推奨されており、iOS と Android の両方でスムーズな動作が保証されています。

その他のライブラリの比較

React Router Native
React Router の React Native 版ですが、2025 年の記事では React Navigation ほど頻繁に推奨されていません。コミュニティの支持も React Navigation に比べて小さいと見られます。
https://www.lambdatest.com/blog/react-component-libraries/

Expo Router
Expo を使用する場合のファイルベースのルーティングソリューションとして注目されていますが、React Navigation の上に構築されており、独立したルーティングライブラリとしては位置付けられません。
https://addjam.com/blog/2025-03-24/react-native-modals-2025-comprehensive-guide/

ライブラリ名 React Native 対応 コミュニティ支持 将来性 特徴
React Navigation 〇(専用設計) 非常に高い 高い スタック、タブ、ドロワーなど多機能、ネイティブ対応、活発な更新
TanStack Router ×(非公式) 中程度 不明 TypeScript 対応、ネストルーティング、React Native 未サポート
React Router Native 低い 中程度 React Router の派生、機能は限定的、コミュニティ支持小さい
Expo Router 〇(Expo 限定) 中程度 中程度 ファイルベースルーティング、React Navigation 依存

状態管理の設計

おすすめは Zustand

Zustand が最もおすすめです。
理由: シンプルで高性能、コミュニティの支持が厚く、React Native に最適化されています。

Zustand は、軽量で使いやすく、不要な再レンダリングを防ぐパフォーマンスが優れています。
特に技術トレンドであるマイクロフロントエンドや SSR に適応しており、長期的なサポートが見込めます。他のライブラリ(Jotai や Redux)と比較しても、コミュニティの規模とリソースの豊富さが際立っています。

Zustand と Jotai の比較

Zustand

コミュニティと人気:
npm trends によると、Zustand は週間のダウンロード数が約 660 万回、GitHub のスター数が 51,604 と非常に人気があります。これは、Jotai(約 140 万ダウンロード、19,783 スター)よりも大きく上回っています。この人気は、長期的なサポートとリソースの豊富さを示唆します。
https://npmtrends.com/jotai-vs-zustand

将来性:
2025 年は Zustand の年とされ、マイクロフロントエンドやサーバーサイドレンダリング(SSR)などのトレンドに適応しています。Why Zustand is Gaining Popularity for State Management in 2025では、Zustand の柔軟性とパフォーマンスが現代のニーズに合致すると評価されています。
https://medium.com/@rigal9979/why-zustand-is-gaining-popularity-for-state-management-in-2025-4e19483c0c6e

Zustand の実装例

  • ストアを一元的に定義し、フックで簡単に状態を操作。
  • ボイラープレートコードが少なく、不要な再レンダリングを防ぐ。
  • React Native での AsyncStorage との統合が簡単。

コード例(To-Do リスト):

// store/todos.js
import create from 'zustand';
import AsyncStorage from '@react-native-async-storage/async-storage';

const useTodoStore = create(set => ({
  todos: [],
  addTodo: text => set(state => ({ todos: [...state.todos, { id: Date.now(), text, completed: false }] })),
  toggleTodo: id =>
    set(state => ({
      todos: state.todos.map(todo => (todo.id === id ? { ...todo, completed: !todo.completed } : todo)),
    })),
  persistTodos: async () => {
    const todos = useTodoStore.getState().todos;
    await AsyncStorage.setItem('todos', JSON.stringify(todos));
  },
}));

export default useTodoStore;
// components/TodoList.js
import React from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import useTodoStore from '../store/todos';

const TodoList = () => {
  const [input, setInput] = React.useState('');
  const { todos, addTodo, toggleTodo, persistTodos } = useTodoStore();

  return (
    <View>
      <TextInput value={input} onChangeText={setInput} placeholder="Add a todo" />
      <Button
        title="Add Todo"
        onPress={() => {
          addTodo(input);
          setInput('');
          persistTodos();
        }}
      />
      {todos.map(todo => (
        <View key={todo.id}>
          <Text
            onPress={() => toggleTodo(todo.id)}
            style={{ textDecorationLine: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </Text>
        </View>
      ))}
    </View>
  );
};

export default TodoList;

Jotai

コミュニティと人気:
Jotai は成長中のライブラリですが、Zustand に比べてコミュニティの規模が小さく、npm のダウンロード数や GitHub のスター数が少ないです。これは、長期的なサポートやリソースの面で Zustand に劣ると考えられます。
将来性:
Jotai は柔軟性があり、将来的な拡張性が高い可能性がありますが、現在のトレンドやコミュニティの支持では Zustand に後れを取っています。Redditの議論では、Jotai は小さな状態共有に適していると評価されていますが、複雑な状態管理には Zustand が推奨される傾向があります。
https://www.reddit.com/r/reactnative/comments/10vvt1g/jotai_or_zustand_for_my_senior_project/

Jotai の実装例

  • アトム単位で状態を管理し、細かい粒度の制御が可能。
  • コードが分散しやすく、小規模プロジェクトでは直感的。
  • 複雑な状態管理ではアトムの依存関係が増え、管理が難しくなる。

コード例(To-Do リスト):

// atoms/todos.js
import { atom } from 'jotai';
import AsyncStorage from '@react-native-async-storage/async-storage';

// アトムの定義
export const todosAtom = atom([]);
export const inputAtom = atom('');

// 派生アトム(例: 完了済みタスクのフィルタリング)
export const completedTodosAtom = atom(get => get(todosAtom).filter(todo => todo.completed));

// アクション(非推奨だが例として)
export const addTodoAtom = atom(null, (get, set, text) => {
  const newTodo = { id: Date.now(), text, completed: false };
  set(todosAtom, [...get(todosAtom), newTodo]);
});
export const toggleTodoAtom = atom(null, (get, set, id) => {
  set(
    todosAtom,
    get(todosAtom).map(todo => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))
  );
});
export const persistTodosAtom = atom(null, async get => {
  const todos = get(todosAtom);
  await AsyncStorage.setItem('todos', JSON.stringify(todos));
});
// components/TodoList.js
import React from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import { useAtom } from 'jotai';
import { todosAtom, inputAtom, addTodoAtom, toggleTodoAtom, persistTodosAtom } from '../atoms/todos';

const TodoList = () => {
  const [todos, setTodos] = useAtom(todosAtom);
  const [input, setInput] = useAtom(inputAtom);
  const [, addTodo] = useAtom(addTodoAtom);
  const [, toggleTodo] = useAtom(toggleTodoAtom);
  const [, persistTodos] = useAtom(persistTodosAtom);

  return (
    <View>
      <TextInput value={input} onChangeText={text => setInput(text)} placeholder="Add a todo" />
      <Button
        title="Add Todo"
        onPress={() => {
          addTodo(input);
          setInput('');
          persistTodos();
        }}
      />
      {todos.map(todo => (
        <View key={todo.id}>
          <Text
            onPress={() => toggleTodo(todo.id)}
            style={{ textDecorationLine: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </Text>
        </View>
      ))}
    </View>
  );
};

export default TodoList;

その他のライブラリ

Redux:
Redux は長年使用されてきた信頼性の高いライブラリですが、ボイラープレートコードが多く、セットアップが複雑です。Redux Toolkit を使用することで簡略化可能ですが、Zustand ほど軽量ではありません。
MobX:
MobX は観察可能な状態を扱うライブラリですが、React Native での使用例が少なく、コミュニティの支持が Zustand ほど強くありません。
Recoil:
開発が止まっています。

データフェッチ

おすすめは TanStack Query

TanStack Query は、React および React Native アプリケーションでのデータフェッチ、キャッシュ管理、同期を効率化する強力なライブラリとして、2025 年時点で最も広く採用されています。

以下の理由から、将来性を見据えた最適な選択肢と考えられます:

  • パフォーマンスと効率性:
    自動キャッシュ管理、バックグラウンドでのデータリフェッチ、ステータス管理(loading/error)をデフォルトで提供し、コード量を大幅に削減。
    React Native での非同期処理をシンプルにし、モバイルアプリの UX 向上に寄与。
  • コミュニティとエコシステム:
    TanStack Query は活発なコミュニティに支えられ、定期的なアップデートと豊富なドキュメントが提供されている。
    React Native だけでなく、React Web や他のフレームワークにも対応する柔軟性があり、クロスプラットフォーム開発に適している。
  • 将来性:
    React Native の「New Architecture」や Hermes エンジンの進化に伴い、TanStack Query は最新の React Native バージョン(例:v0.78)との互換性を維持。
    サーバー状態管理の標準として、SWR や RTK Query などの代替ライブラリと比較しても採用率が高く、長期的なメンテナンスが期待できる。
  • React Native 特有の利点:
    モバイル環境でのオフライン対応やエラーハンドリングが容易。
    Expo や React Native CLI の両方でスムーズに動作し、開発環境に依存しない。

実装例:

import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { View, Text } from 'react-native';

const queryClient = new QueryClient();

const fetchData = async () => {
  const response = await fetch('https://api.example.com/data');
  return response.json();
};

const DataComponent = () => {
  const { data, isLoading, error } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
  });

  if (isLoading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error.message}</Text>;

  return (
    <View>
      <Text>{JSON.stringify(data)}</Text>
    </View>
  );
};

const App = () => (
  <QueryClientProvider client={queryClient}>
    <DataComponent />
  </QueryClientProvider>
);

export default App;

他のライブラリとの比較

https://www.robinwieruch.de/react-libraries/
https://dev.to/myogeshchavan97/top-10-react-librariesframeworks-for-2025-50i4
https://zenn.dev/cybozu_frontend/articles/f1f03e69946177
https://qiita.com/ozora/items/33e00dcaca372db04560

機能/能力 TanStack Query SWR Apollo Client
プラットフォーム要件 React React React, GraphQL
サポートクエリ構文 Promise, REST, GraphQL Promise, REST, GraphQL GraphQL, Any (Reactive Variables)
キャッシング戦略 階層的キー->値 ユニークキー->値 正規化スキーマ
キャッシュ変更検出 深い比較 (安定シリアル化) 深い比較 (安定シリアル化) 深い比較 (不安定シリアル化)
無限クエリ 🔶 🔶
クエリキャンセル 🛑 🛑
自動ガベージコレクション 🛑 🛑
React Native サポート

(✅: サポート、🔶: 部分サポート、🛑: 非サポート)

ローカル DB

おすすめは Realm

なぜ Realm がおすすめか
Realm は、高速なクエリ処理と強力なオフライン対応で知られ、React Native アプリ開発で広く支持されています。セキュリティ面でも AES-256 などの暗号化をサポートし、大規模なデータ管理に適しています。2024 年の情報では、Realm はトップの選択肢として頻繁に挙げられており、2025 年でもその地位を維持している可能性が高いです。

その他のライブラリ
WatermelonDB:
特に大規模なデータセットを効率的に扱う場合に優れていますが、ドキュメントが少ない点が課題です。

AsyncStorage:
シンプルですが、大量のデータには不向きで、2021 年時点で非推奨とされています。

ローカルデータベース比較

データベース タイプ Expo Go 対応 メリット デメリット 価格 おすすめのユースケース
AsyncStorage キー-バリューストア ✔️ シンプルな API、Expo に標準搭載、軽量 大量データに不向き、React Native コアで非推奨 無料、オープンソース 少量データ(例:設定、トークン)
expo-sqlite リレーショナル (SQLite) ✔️ ACID 準拠、SQL クエリ対応、ORM(例:Drizzle)と統合可能 セットアップが複雑、一部デバイス(例:iPad)で問題発生の可能性 無料、オープンソース 中~大量の構造化データ
Realm オブジェクト指向 ❌ (開発ビルド) 高パフォーマンス(SQL 比 10 倍)、強力な暗号化(AES-256) データ型の対応が限定的、Expo では開発ビルドが必要 無料(小規模アプリ)、有料プラン 高パフォーマンス、オフライン重視のアプリ
WatermelonDB SQL (SQLite ベース) ❌ (開発ビルド) 大規模データに効率的、リアクティブ、オフライン対応 ドキュメントが不足、Expo では開発ビルドが必要 無料、オープンソース 大規模データ、リアクティブなアプリ
PouchDB NoSQL、JSON ⚠️ (要テスト) CouchDB と同期、オフライン対応 セットアップが複雑、Expo Go の互換性が不明 無料、オープンソース CouchDB 同期が必要なアプリ
Couchbase Lite NoSQL ❌ (開発ビルド) オフライン優先、双方向同期 セットアップが複雑、Expo では開発ビルドが必要 無料(小規模アプリ)、有料プラン 同期要件の多いアプリ
Vasern 軽量 NoSQL ⚠️ (要テスト) 高速、シンプルな API、エンドツーエンド同期 開発途上、機能が限定的、Expo Go の互換性が不明 無料、オープンソース 軽量アプリ、シンプルなデータニーズ

将来を見据えた場合、以下の要素を考慮しました:

  • コミュニティのサポート: Realm は長年の歴史があり、大きなコミュニティと継続的なアップデートが期待できます。
  • 新機能の導入: WatermelonDB は React Native に特化しており、将来的に成長する可能性がありますが、2024 年時点ではドキュメントが不足。
  • オフライン対応と同期: Realm と Couchbase Lite はオフライン機能と同期に優れ、モバイルアプリのトレンドに合致。
  • セキュリティ: Realm の AES-256 暗号化は、プライバシー規制が厳しくなる将来に適しています。

スタイリング設計パターン

UI ライブラリ

Shopify Restyle がおすすめ

Shopify Restyle は、TypeScript との相性が良く、テーマ作成に特化しており、2025 年のベンチマークでは React Native の内蔵 StyleSheet と比較して-12.05%のスローダウンという優れた結果を示しました。また、Shopify が 2025 年も積極的に使用・推進しているため、長期的なサポートが期待できます。

他のライブラリとの比較

以下の基準でライブラリを評価しました:

性能: ベンチマーク結果やコミュニティのフィードバック。
将来性: メンテナンス状況や企業・コミュニティのサポート。
互換性: React Native との専用設計。
機能性: スタイリングに特化した機能(テーマ作成、カスタマイズ性など)。

1. Shopify Restyle
特徴: TypeScript を活用した UI コンポーネントの構築に特化。テーマ作成が中心で、カスタマイズ性が高い。
性能: 2025 年のベンチマークでは、React Native の内蔵 StyleSheet と比較して-12.05%のスローダウン。250 アイテムのレンダリングで優れた結果。
将来性: Shopify が 2025 年も積極的に使用・推進。企業バックアップにより、長期的なメンテナンスが期待。
コミュニティ: Shopify の公式ドキュメントがあり、開発者コミュニティでの支持も強い。

2. NativeWind
特徴: Tailwind CSS にインスパイアされたユーティリティファーストのスタイリング。プラットフォームごとに最適なスタイルエンジンを使用。
性能: 同ベンチマークでは-0.84%のスローダウン。Shopify Restyle ほどではないが、ほぼネイティブに近い性能。
将来性: 2025 年でもアクティブで、開発者コミュニティ(Reddit、DEV Community)で議論されている
コミュニティ: インストール数や GitHub スター数から人気があるが、企業バックアップは Shopify Restyle ほど強くない。

3. gluestack UI
特徴: NativeBase の後継で、性能と効率に焦点。30 以上のカスタマイズ可能なコンポーネントとスタイリングユーティリティを提供。
性能: 具体的なベンチマークデータは見つからなかったが、LogRocket や instamobile の 2025 年記事でトップライブラリとして言及
将来性: NativeBase の後継として注目されているが、専用のスタイリングライブラリではなく、UI ライブラリとしての位置付け。

その他のライブラリ:
React Native Paper や React Native Elements は UI コンポーネントライブラリとしてスタイリング機能を持つが、専用のスタイリングライブラリではないため、今回の評価から除外。
記事では、Shopify Restyle や NativeWind が 2024 年に評価されており、2025 年でも関連性が続く可能性が高い。

共通スタイル

https://callstack.github.io/react-native-paper/docs/guides/theming/
https://shopify.engineering/5-ways-to-improve-your-react-native-styling-workflow

React Native でのテーマ管理は、アプリ全体で一貫した外観を保つために重要です。テーマを利用することで、色、フォント、間隔などのスタイルを一元管理し、開発効率とユーザー体験を向上させます。

スタイルを統一化するためにいかが必要となります。

  • グローバルテーマオブジェクトの作成: 色、間隔、フォントなどのスタイルを一元管理するテーマオブジェクトを定義します。これにより、スタイルの再利用性と保守性が向上します。
  • テーマプロバイダーの使用: ThemeProvider などのコンポーネントを使用して、テーマをアプリ全体に適用します。これにより、すべてのコンポーネントがテーマにアクセス可能になります。
  • デザインシステムの採用: テーマは、色や間隔の値を標準化するデザインシステムに基づいて構築します。たとえば、Polaris Color Palette のようなパレットを使用。
  • ライト/ダークモード対応: React Native の Appearance モジュールを活用し、ユーザーの好みに応じたテーマ切り替えを実装します。
  • レスポンシブデザイン: 異なるデバイスサイズに対応するため、レスポンシブプロップやブレークポイントを定義します。
  • 型安全性の確保: TypeScript を使用して、テーマ値の誤使用を防ぎ、開発効率を向上させます。

Shopify Restyle でのテーマ定義の例

Shopify Restyle では、createTheme 関数を使用してグローバルテーマオブジェクトを定義します。

import { createTheme } from '@shopify/restyle';

const palette = {
  purpleLight: '#8C6FF7',
  purplePrimary: '#5A31F4',
  purpleDark: '#3F22AB',
  greenLight: '#56DCBA',
  greenPrimary: '#0ECD9D',
  greenDark: '#0A906E',
  black: '#0B0B0B',
  white: '#F0F2F3',
};

const theme = createTheme({
  colors: {
    mainBackground: palette.white,
    cardPrimaryBackground: palette.purplePrimary,
  },
  spacing: {
    s: 8,
    m: 16,
    l: 24,
    xl: 40,
  },
  textVariants: {
    header: {
      fontWeight: 'bold',
      fontSize: 34,
    },
    body: {
      fontSize: 16,
      lineHeight: 24,
    },
    defaults: {},
  },
});

export type Theme = typeof theme;
export default theme;

このテーマは、ThemeProvider を介してアプリ全体に適用されます

import { ThemeProvider } from '@shopify/restyle';
import theme from './theme';

const App = () => <ThemeProvider theme={theme}>{/* アプリのコンポーネント */}</ThemeProvider>;

export default App;

グローバルスタイル: Restyle のテーマオブジェクトは、グローバルスタイルの中心です。たとえば、colors.mainBackground をアプリの背景色として使用することで、すべての画面で一貫した外観を保ちます。
共通スタイル: BoxText コンポーネントを使用し、テーマ値(例:spacing.m)を参照することで、共通スタイルを適用します。また、バリアントを使用して、特定のコンポーネントに異なるスタイルを定義できます。

例: Card

import { createBox, createText, createRestyleComponent, createVariant, VariantProps } from '@shopify/restyle';
import { Theme } from './theme';

const Box = createBox<Theme>();
const Text = createText<Theme>();
const Card = createRestyleComponent<VariantProps<Theme, 'cardVariants'> & React.ComponentProps<typeof Box>, Theme>(
  [createVariant({ themeKey: 'cardVariants' })],
  Box
);

const CardComponent = ({ variant = 'primary' }) => {
  return (
    <Card margin="s" variant={variant}>
      <Text variant="body">This is a card</Text>
    </Card>
  );
};

export default CardComponent;

他のライブラリとの比較

ライブラリ テーマ管理の特徴 メリット デメリット
Shopify Restyle グローバルテーマオブジェクト、ThemeProviderを使用 型安全性、テーマ特化、性能良好(-12.05%スローダウン) 学習コストがやや高い
React Native Paper PaperProvider、Material You テーマ 簡単に統合可能、UI コンポーネント豊富 スタイリングの柔軟性がやや低い
React Native Elements ThemeProvider、カスタムテーマ シンプルなテーマ適用 スタイリング専用ではない
NativeWind Tailwind CSS 風のユーティリティ 迅速なスタイリング、ほぼネイティブに近い性能(-0.84%スローダウン) テーマ管理機能が限定的

パフォーマンス最適化

https://legacy.reactjs.org/docs/optimizing-performance.html
https://reactnative.dev/docs/performance

再レンダリングの防止

React Native では、PureComponent や React.memo を使うと、プロパティや状態が変更されていない場合にコンポーネントの再レンダリングを防げます。これにより、特に複雑なアプリでパフォーマンスが向上する可能性があります。

PureComponent/React.memo の利用

PureComponent はクラスコンポーネント向けで、props と state の浅い比較を行い、変更がない場合に再レンダリングを防ぎます。一方、React.memo は関数コンポーネント向けで、props に基づいてコンポーネントをメモ化します。これにより、特に深いコンポーネントツリーを持つアプリでパフォーマンスが向上します。

具体的な手法: コンポーネントが純粋関数(入力が同じであれば出力が同じ)である場合、これらの手法を適用します。例えば、リストアイテムが親コンポーネントの状態に依存しない場合、React.memo でラップすることで再レダリングを防げます。

リストの最適化

FlatList や SectionList を使う場合、keyExtractor で各アイテムにユニークなキーを設定し、renderItem を useCallback でメモ化することで、パフォーマンスを改善できます。また、getItemLayout を使うと、アイテムの高さを事前に計算でき、スクロールがスムーズになります。

FlatList/SectionList の最適化

https://reactnative.dev/docs/optimizing-flatlist-configuration

FlatList と SectionList は、大規模なリストを効率的にレンダリングするための React Native のコンポーネントです。これらのパフォーマンスを最適化することで、スムーズなスクロールと応答性を確保できます。

具体的な手法:

  • keyExtractor: 各リストアイテムにユニークなキーを提供し、React Native が変更を効率的に追跡できるようにします。

  • renderItem のメモ化: renderItem をコンポーネント外で定義するか、useCallback を使ってメモ化することで、毎回のレンダリングで関数が再作成されるのを防ぎます。

  • getItemLayout: すべてのアイテムが同じ高さの場合、getItemLayout を指定することで、動的な高さ計算を避け、レンダリング速度を向上させます。

  • レンダリングプロパティの調整: initialNumToRender で初期レンダリングアイテム数を制限し、windowSize で可視範囲外のレンダリングアイテム数を制御します。また、maxToRenderPerBatch で一度にレンダリングするアイテム数を制限します。

  • リストアイテムの最適化: リストアイテム自体を PureComponent や React.memo でラップし、不要な再レンダリングを防ぎます。

画像の最適化

https://reactnativetips.com/optimizing-image-performance-in-react-native/

画像はアプリのパフォーマンスに大きな影響を与えます。画像を必要なサイズにリサイズし、WebP などの効率的な形式を使うことで、メモリ使用量を減らし、ロード時間を短縮できます。react-native-fast-image のようなライブラリも役立ちます。
具体的な手法:

  • リサイズ

    • 画像をアプリで表示する必要な寸法に事前にリサイズ
    • メモリ使用量と処理時間を削減
  • 効率的な形式の使用

    • 写真には JPEG を使用
    • 透明性が必要な画像には PNG を使用
    • 圧縮効率の高い WebP 形式を検討(React Native でサポート)
  • キャッシングの活用

    • react-native-fast-image のようなライブラリを使用
    • 画像のキャッシングとロードを最適化
  • 大きな画像の回避

    • 過剰に大きな画像のロードを防止
    • レンダリングの遅延を防ぐ
    • メモリ使用量の増加を抑制
    • 適切なサイズの画像を使用

Hermes エンジンの活用

https://reactnative.dev/docs/hermes

Hermes は React Native 向けに最適化された JavaScript エンジンで、起動時間を短縮し、メモリ使用量を減らし、アプリサイズを小さくします。最新の React Native ではデフォルトで有効になっています。
具体的な手法:

  • デフォルトの有効化

    • React Native 0.70 以降のバージョンでデフォルトで有効
    • 古いバージョンの場合はドキュメントに従って手動で有効化が必要
  • パフォーマンス向上のポイント

    • モバイル環境向けに最適化された設計
    • リソースが限られたデバイスでの性能が向上
    • 起動時間の短縮
    • メモリフットプリントの削減
    • アプリサイズの縮小

Expo 環境での最適化

https://docs.expo.dev/guides/overview/

Expo では、パフォーマンス監視ツールを使ってボトルネックを特定し、実デバイスでテストすることで、実際の使用環境でのパフォーマンスを評価できます。Expo の管理ワークフローの制約にも注意が必要です。

  • パフォーマンス監視ツールの利用: Expo Dev Tools を使って、メモリ使用量、JavaScript ヒープサイズ、フレームレートなどのパフォーマンスメトリクスを監視します。

  • 実デバイスでのテスト: エミュレータと実デバイスのパフォーマンスは異なる場合があるため、実際の使用環境でのパフォーマンスを評価するために実デバイスでテストします。

  • アセットの最適化: 画像やフォントなどのアセットを最適化し、バンドルサイズを最小限に抑えます。

  • 管理ワークフローの制約: Expo の管理ワークフローはネイティブモジュールのアクセスを制限する場合がありますが、JavaScript ベースの最適化は適用可能です。

終わりに

私自身、React Nativeを触り始めてかなり日が浅いので、肌感が分からなかったので勉強がてら書いてみました。
grok, gpt, geminiなどを使い分けつつdeep researchした情報をまとめました。
適宜修正を加えていますが、文章自体はほとんど自分で書いてません。
"expo routerの方が現場では使われてて、React navigationは複雑になるから使われてないよ"っとかあったらベテランの方教えてください!


https://www.humanhacker.ai/

Discussion

thayashi_narusethayashi_naruse

素晴らしいです!ありがとうございます 🙇

mm

温かいコメントありがとうございます!励みになります!
気になる点やご質問があれば、ぜひお気軽にお知らせください