手動ナビゲーション設定という悪夢
React Nativeの初期から苦労してきた方なら、react-navigationはお馴染みでしょう。非常に強力ですが、App.jsファイル内で各画面を手動で定義するのはまさに苦行です。設定ファイルは何百行、時には何千行にも膨れ上がります。新機能を追加するたびにそのファイルに戻って宣言しなければならず、プロジェクトが大きくなるにつれて非常に時間がかかり、ミスも起こりやすくなります。
Expo Routerの登場により、状況は一変しました。Next.jsの「ファイルベースルーティング」の考え方をそのままモバイル環境に持ち込んだのです。簡単に言えば、ディレクトリ内にファイルを作成するだけで、Expoがそれをルートとして自動的に認識します。Navigatorを手動でインポート/エクスポートする必要はもうありません。ここでは、実際のプロジェクトで標準的なディレクトリ構造を構築するために、私がどのようにExpo Routerを活用しているかをご紹介します。
なぜ Expo Routerが新たな「最適解」なのか?
本質的に、Expo Routerはreact-navigation上で動作しますが、完全に自動化されています。コードを書いてパスを定義する代わりに、フォルダ構造そのものを使用します。
私がこの技術にすぐに惚れ込んだ3つのポイントを紹介します:
- Native-first: iOSとAndroidの両方でスムーズなパフォーマンスを最適化。
- Deep Linkingがデフォルト: 通常、Deep Linkingの設定には半日かかりますが、Expo RouterではファイルパスがそのままURLになります。設定なしですぐに動作します。
- 直感的な構造:
app/フォルダを見るだけで、アプリにいくつ画面があるか一目でわかります。メンテナンスが非常に容易です。
プロジェクトの標準的なディレクトリ構造を構築する
まずは、お馴染みのコマンドで新しいExpoプロジェクトを作成しましょう:
npx create-expo-app@latest my-new-app
このエコシステムでは、すべてのナビゲーションロジックがapp/ディレクトリ内に収まります。以下は、私が頻繁に採用している構成案です:
app/
├── (auth)/
│ ├── login.tsx
│ └── register.tsx
├── (tabs)/
│ ├── index.tsx (ホーム)
│ ├── settings.tsx
│ └── _layout.tsx
├── user/
│ └── [id].tsx (ユーザー詳細ページ)
├── _layout.tsx (全体レイアウト)
└── +not-found.tsx (404ページ)
1. _layout.tsxファイル – アプリの骨格
_layout.tsxファイルは「背骨」のような役割を果たします。ヘッダー、フッター、またはProviderなどの共通コンポーネントを保持します。
一番外側のapp/_layout.tsxでは、以下のようにメインのStackを設定することが多いです:
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="(auth)" options={{ presentation: 'modal' }} />
<Stack.Screen name="user/[id]" options={{ title: '個人情報' }} />
</Stack>
);
}
2. Group Routes:括弧 () を使ったテクニック
なぜ(auth)や(tabs)を使っているのか疑問に思うかもしれません。括弧を使うと、URLに影響を与えることなくファイルをグループ化して整理できます。
例えば、app/(auth)/login.tsxファイルのパスは、/auth/loginではなく/loginになります。これにより、ディレクトリ構造は非常にクリーンに保ちつつ、URLは短くプロフェッショナルなまま維持できます。
3. Tabs Layout – ボトムナビゲーションバー
タブバーを作成するには、app/(tabs)/_layout.tsxファイルを使用します。Expo Routerは非常に強力なTabsコンポーネントを標準で提供しています:
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: '#007AFF' }}>
<Tabs.Screen
name="index"
options={{
title: 'ホーム',
tabBarIcon: ({ color }) => <Ionicons name="home" size={28} color={color} />,
}}
/>
<Tabs.Screen
name="settings"
options={{
title: '設定',
tabBarIcon: ({ color }) => <Ionicons name="settings" size={28} color={color} />,
}}
/>
</Tabs>
);
}
Dynamic Routes:詳細ページを柔軟に処理する
商品詳細やプロフィールページのような画面では、ファイル名を[id].tsxにするだけです。
app/user/[id].tsxファイルの例を見てみましょう:
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';
export default function UserDetail() {
const { id } = useLocalSearchParams();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>ユーザーID: {id}</Text>
</View>
);
}
/user/123に遷移すると、システムが自動的にid = 123を割り当てます。非常にスピーディーで簡潔です!
データ処理における「実戦」の経験
実際には、複雑なJSON文字列を返すAPIを扱うことがよくあります。console.logを出力して目を凝らして読むのは、非常に時間がかかります。
私はよくtoolcraft.appのツール(例:JSON Formatter)を使ってデータを整形します。これにより、TypeScriptのインターフェースを30秒で定義でき、Dynamic Routes作成時の不快なundefinedエラーを完全に防ぐことができます。
もう一つのコツ:useRouterフックよりもLinkコンポーネントを優先して使用しましょう。コードがHTMLのようになり、非常に読みやすくメンテナンスしやすくなります:
import { Link } from 'expo-router';
<Link href="/user/99" asChild>
<Button title="プロフィールを見る" />
</Link>
まとめ
ファイル名の付け方が独特なので、最初は少し戸惑うかもしれません。しかし、慣れてしまえば、不要なボイラープレートコードを30〜40%削減できることがわかるでしょう。ぜひ、私が提案した構造を次のプロジェクトに適用してみてください。間違いなく違いを実感できるはずです。それでは、ハッピーコーディング!

