React Native推奨構成
2026年時点でのReact Nativeアプリ開発における推奨技術スタックと構成をまとめる。
この構成は、Reactエンジニアが既存のWeb開発知識を活かしながら、モバイルアプリを効率的に開発できることを重視している。
推奨技術スタック
新規プロジェクトで採用すべき技術スタックは以下の通りである。
| レイヤー | 推奨 | 立ち位置 |
|---|---|---|
| 言語 | TypeScript | 標準 |
| フレームワーク | Expo | Next.js + Vercelの役割を果たす |
| サーバー状態管理 | TanStack Query | Webと同じものがそのまま動く |
| クライアント状態管理 | Zustand | Webと同じものがそのまま動く |
| API通信 | fetch(+ Hono RPC) | Webと同じ感覚で書ける |
| フォーム | React Hook Form + Zod | Webと同じパターン |
| スタイリング | NativeWind | RN版Tailwind CSS |
| UIコンポーネント | gluestack-ui | RN版shadcn/ui |
| ローカルストレージ | MMKV | localStorageの超高速版 |
| バックエンド/認証 | Supabase | Firebase的BaaS |
なぜこの構成なのか
この技術スタックの最大の特徴は、Web開発の知識をそのまま再利用できる点にある。
- TanStack Query、Zustand、React Hook Form、Zodは、Webで使っているものと完全に同じ
- Expo RouterはNext.jsのApp Routerに似たファイルベースルーティング
- NativeWindはTailwind CSSの書き味を再現
- gluestack-uiはshadcn/uiと同じコピペ方式
- Hono / SupabaseはWebのバックエンド構成と共通
アーキテクチャの理解
New Architecture
React Native 0.76から、New Architectureがデフォルトになった。これにより以下が実現された。
- JSI(JavaScript Interface): JavaScriptとネイティブ間で直接参照を持てる仕組み
- Fabric: 新しいUI描画エンジン(React 18の並行レンダリングに対応)
- TurboModules: 遅延読み込み方式のNative Module
- Codegen: 型安全なバインディングコードの自動生成
新規プロジェクトでは特別な設定は不要で、最新のExpo SDKを使えば自動的にNew Architecture上で動作する。
主要ライブラリの使い方
Expo
React Nativeアプリ開発の標準レールとなっている。
開発時の利点
- Expo Go: QRコードで実機確認(Xcode不要)
- Development Build: アプリ固有の設定を反映した開発ビルド
- CNG(Continuous Native Generation): 設定ファイル(
app.json)からネイティブプロジェクトを自動生成
配信時の利点(EAS)
- EAS Build: クラウドでビルド実行(Macなしでも可能)
- EAS Submit: ストア提出をコマンド1つで完了
- EAS Update: JavaScriptの更新をOTA配信(審査不要)
Expo Router
ファイルベースルーティングを実現する。
app/
├── _layout.tsx # ルートレイアウト
├── (tabs)/ # タブグループ
│ ├── _layout.tsx # タブナビゲーション定義
│ ├── index.tsx # ホームタブ
│ └── settings.tsx # 設定タブ
└── users/
└── [id].tsx # 動的ルート
状態管理の役割分担
状態は大きく2種類に分ける。
- Server State: サーバーに本体があるデータ → TanStack Query
- Client State: アプリ内に本体がある状態 → Zustand
TanStack Queryの例
import { useQuery } from '@tanstack/react-query';
import { z } from 'zod';
const profileSchema = z.object({
id: z.string(),
name: z.string(),
avatarUrl: z.url().nullable(),
});
async function fetchProfile(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}`);
if (!res.ok) throw new Error('プロフィールの取得に失敗しました');
return profileSchema.parse(await res.json());
}
export function useProfile(userId: string) {
return useQuery({
queryKey: ['profile', userId],
queryFn: () => fetchProfile(userId),
});
}
Zustandの例
import { create } from 'zustand';
type Theme = 'light' | 'dark' | 'system';
type ThemeState = {
theme: Theme;
setTheme: (theme: Theme) => void;
};
export const useThemeStore = create<ThemeState>((set) => ({
theme: 'system',
setTheme: (theme) => set({ theme }),
}));
API通信とHono RPC
バックエンドも自分で書く場合、Honoを使うと型安全なRPCが実現できる。
サーバー側
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const app = new Hono()
.get('/todos', (c) => {
return c.json({ todos: [{ id: 1, title: '牛乳を買う', done: false }] });
})
.post(
'/todos',
zValidator('json', z.object({ title: z.string().min(1) })),
(c) => {
const { title } = c.req.valid('json');
return c.json({ ok: true, title }, 201);
}
);
export type AppType = typeof app;
クライアント側
import { hc } from 'hono/client';
import type { AppType } from '../../../server';
export const client = hc<AppType>('https://api.example.com');
const { data } = useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await client.todos.$get();
return res.json(); // 戻り値が完全に型付き
},
});
フォーム管理
React Hook Form + Zodの組み合わせをそのまま使用できる。
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { TextInput, Text, Button, View } from "react-native";
const schema = z.object({
email: z.email("メールアドレスの形式が正しくありません"),
password: z.string().min(8, "8文字以上で入力してください"),
});
type FormData = z.infer<typeof schema>;
export function SignInForm() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
resolver: zodResolver(schema),
});
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<View>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextInput
ref={ref}
value={value}
onChangeText={onChange}
onBlur={onBlur}
placeholder="メールアドレス"
autoCapitalize="none"
keyboardType="email-address"
/>
)}
/>
{errors.email && <Text>{errors.email.message}</Text>}
<Button title="ログイン" onPress={handleSubmit(onSubmit)} />
</View>
);
}
スタイリング
NativeWind
Tailwind CSSのutility-first書き方をReact Nativeで実現する。
import { Text, View } from 'react-native';
export function Card() {
return (
<View className="rounded-2xl bg-white p-4 shadow-sm">
<Text className="text-lg font-bold text-zinc-900">
React Native、だいぶ優しくなったね
</Text>
<Text className="mt-2 text-sm text-zinc-600">
でもAndroidの端末差分はまだたまに牙をむく
</Text>
</View>
);
}
gluestack-ui
shadcn/ui流のコピペ方式でコンポーネントを管理する。
# プロジェクト初期化
npx gluestack-ui@latest init
# コンポーネント追加
npx gluestack-ui@latest add button
コードがcomponents/ui/以下に生成されるため、自由にカスタマイズできる。
ローカルストレージ
MMKV(react-native-mmkv)を使用する。AsyncStorageより約30倍高速で、同期的に読み書きできる。
import { createMMKV } from 'react-native-mmkv';
export const storage = createMMKV();
// awaitなしで同期的に使える
storage.set('onboardingDone', true);
const done = storage.getBoolean('onboardingDone');
Zustandのpersistミドルウェアと組み合わせると、設定値を永続化できる。
バックエンドと認証
個人開発や小中規模プロダクトでは、Supabaseが有力候補である。
import 'react-native-url-polyfill/auto';
import { createClient } from '@supabase/supabase-js';
import 'expo-sqlite/localStorage/install';
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!;
const supabasePublishableKey =
process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY!;
export const supabase = createClient(supabaseUrl, supabasePublishableKey, {
auth: {
storage: localStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
});
// メール・パスワード認証
const { error } = await supabase.auth.signInWithPassword({ email, password });
推奨ディレクトリ構成
機能単位で整理する構成を推奨する。
my-rn-app/
├── app/ # Expo Router(ルーティング)
│ ├── _layout.tsx # ルートレイアウト
│ ├── +not-found.tsx # 404相当
│ ├── (auth)/ # 認証画面
│ │ ├── sign-in.tsx
│ │ └── sign-up.tsx
│ └── (tabs)/ # タブ画面
│ ├── _layout.tsx
│ ├── index.tsx
│ └── settings.tsx
├── assets/ # 静的リソース
│ ├── images/
│ └── fonts/
├── src/
│ ├── features/ # 機能単位で縦に切る
│ │ └── todo/
│ │ ├── api.ts # API呼び出し
│ │ ├── hooks.ts # TanStack Query hooks
│ │ ├── schema.ts # Zodスキーマ
│ │ └── components/ # 機能専用UI
│ ├── shared/ # 機能をまたぐ共通レイヤ
│ │ ├── api/
│ │ │ ├── client.ts # APIクライアント
│ │ │ └── queryClient.ts # TanStack Query設定
│ │ ├── components/
│ │ │ └── ui/ # gluestack-ui
│ │ ├── lib/
│ │ │ ├── supabase.ts
│ │ │ └── storage.ts # MMKVラッパー
│ │ ├── styles/
│ │ │ └── theme.ts
│ │ └── utils/
│ └── stores/ # Zustand
│ └── useSettingsStore.ts
├── app.json # Expo設定
├── eas.json # EASビルド設定
├── global.css # NativeWind CSS
└── metro.config.js # Metro設定
設計原則
app/は画面の置き場に徹する: ロジックはsrc/features/へ配置- 依存の方向は一方通行:
features/→shared/のimportはOK、逆は禁止 - 機能単位で縦に切る:
api.ts/hooks.ts/schema.tsを機能ごとに揃える
まとめ
2026年のReact Native開発環境は、以下の点でWeb開発者に優しくなっている。
- TypeScript、TanStack Query、Zustand、React Hook Form、ZodなどWeb開発の定番ライブラリがそのまま使える
- Expo RouterによりNext.js App Routerに似たファイルベース開発が可能
- NativeWindでTailwind CSSの書き味を再現
- gluestack-uiでshadcn/uiと同じ開発フローを実現
- EASによりビルド・配信・更新の「行政手続き」が大幅に簡略化
既存のWeb開発知識を活かしながら、モバイルアプリ開発に参入できる環境が整っている。