ナビゲーション
2026 現在の RN ナビゲーションの定番は Expo Router(ファイルベース)。 裏側は React Navigation を使っているので、深いカスタマイズは React Navigation の API に降りる。
2 つのアプローチ
| Expo Router | React Navigation 直書き | |
|---|---|---|
| 定義 | ファイルパス = ルート | JS でツリー手書き |
| Deep Link | 自動 | 手動設定 |
| Web 対応 | ○(Next.js 風の出力) | △ |
| typed routes | ○(自動生成) | 手書き |
| 新規プロジェクト | これが標準 | — |
Expo Router の基本
app/ 配下のファイル構造がそのままルート。Next.js App Router に似ている。
app/
├── _layout.tsx ← ルートレイアウト(全画面共通)
├── index.tsx ← /
├── about.tsx ← /about
├── posts/
│ ├── _layout.tsx ← /posts 配下のレイアウト
│ ├── index.tsx ← /posts
│ └── [id].tsx ← /posts/123(動的)
├── (tabs)/ ← URL に出ないグループ(タブ表示)
│ ├── _layout.tsx
│ ├── index.tsx
│ └── settings.tsx
└── +not-found.tsx ← 404
ルートレイアウト
全画面共通の枠を _layout.tsx で定義。<Slot /> または <Stack /> 等のナビゲータを置く。
import { Stack } from "expo-router"
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: "ホーム" }} />
<Stack.Screen name="about" options={{ title: "About" }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
)
}
ナビゲータの種類
<Stack> — 階層遷移
画面が積み重なる定番。前の画面に戻れる(戻るボタンが自動)。
import { Stack } from "expo-router"
<Stack screenOptions={{ headerStyle: { backgroundColor: "#0099ff" } }} />
<Tabs> — 下タブ
モバイルで定番の下タブ。(tabs) グループで囲って _layout に <Tabs>。
import { Tabs } from "expo-router"
export default function TabsLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: "#0099ff" }}>
<Tabs.Screen
name="index"
options={{ title: "ホーム", tabBarIcon: ({ color }) => <HomeIcon color={color} /> }}
/>
<Tabs.Screen
name="settings"
options={{ title: "設定", tabBarIcon: ({ color }) => <CogIcon color={color} /> }}
/>
</Tabs>
)
}
<Drawer> — サイドメニュー
左から引き出すメニュー。同様にレイアウトファイルで宣言。
<Slot> — シンプル枠
ナビゲーションの仕組みを使わない「ただ子を表示する」枠。コンテキストプロバイダ等を上に置く時に使う。
遷移する
リンクとして書く(推奨)
import { Link } from "expo-router"
<Link href="/about">About へ</Link>
// パラメータ付き
<Link href={{ pathname: "/posts/[id]", params: { id: "123" } }}>
Post 123
</Link>
// asChild で任意のコンポーネントをタップ可能に
<Link href="/about" asChild>
<Pressable><Text>About</Text></Pressable>
</Link>
命令的に遷移
import { router } from "expo-router"
router.push("/about") // 履歴に積む
router.replace("/login") // 現在の画面を置換
router.back() // 戻る
router.dismiss(2) // モーダル/Stack を 2 つ閉じる
router.setParams({ filter: "new" }) // 現在の URL のクエリを更新
パラメータの取得
import { useLocalSearchParams } from "expo-router"
import { Text } from "react-native"
export default function Post() {
const { id } = useLocalSearchParams<{ id: string }>()
return <Text>Post #{id}</Text>
}
useLocalSearchParams— そのファイルが受け取るパラメータuseGlobalSearchParams— どの URL か関係なく現在のパラメータ
Typed Routes(型安全な URL)
app.json で experiments.typedRoutes: true を有効化すると、
Link や router.push の引数が型チェックされる。
{
"expo": {
"experiments": { "typedRoutes": true }
}
}
モーダル
Stack.Screen の options で presentation: "modal" を指定すると、
下からせり上がるネイティブモーダルとして表示される。
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="login" options={{ presentation: "modal" }} />
</Stack>
ヘッダのカスタマイズ
<Stack.Screen
name="post"
options={{
title: "投稿詳細",
headerStyle: { backgroundColor: "#0099ff" },
headerTintColor: "#fff",
headerTitleStyle: { fontWeight: "600" },
headerLeft: () => <BackButton />,
headerRight: () => <ShareButton />,
headerBackTitle: "戻る",
headerShown: true,
}}
/>
画面コンポーネント内から動的に変えたい時は <Stack.Screen options={{ ... }} /> をその場で書く。
戻るを止める
import { useNavigation } from "expo-router"
useEffect(() => {
return navigation.addListener("beforeRemove", (e) => {
if (!hasUnsavedChanges) return
e.preventDefault()
Alert.alert("未保存の変更があります", "破棄しますか?", [
{ text: "キャンセル" },
{ text: "破棄", onPress: () => navigation.dispatch(e.data.action) },
])
})
}, [hasUnsavedChanges])
Deep Link / Universal Link
app.json の scheme で URL スキームを設定すると、myapp://post/123 のような
URL でアプリが開く。Expo Router はそのまま該当ルートに飛ぶので、何もしなくて良い。
Universal Link / App Links(https URL)
- iOS:
app.jsonにios.associatedDomains: ["applinks:example.com"] - Android:
android.intentFilters設定 - サーバ側に
apple-app-site-association/assetlinks.jsonを配置
Universal Link にすると、メールやSlackで貼った https://example.com/post/123 から直接アプリに飛べる。
状態の保持と復元
Expo Router は 戻る時の画面状態を自動で保持。アプリを完全に再起動しても URL が復元される
(app.json の scheme 設定が前提)。
Web 対応
npx expo start --web または w で Web ビルドが見られる。Expo Router は Web で SPA として動く。
ルート構造がそのまま URL になるので、ネイティブと Web で同じ構造のアプリが書ける。
素の React Navigation で書く
Expo Router を使わない場合の典型例:
import { NavigationContainer } from "@react-navigation/native"
import { createNativeStackNavigator } from "@react-navigation/native-stack"
const Stack = createNativeStackNavigator()
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
新規プロジェクトでこちらを選ぶ理由はほぼない。Expo Router の上に React Navigation の API も生で 使えるので、必要に応じてその一部だけ降りる。
Stack.Screen の animation オプションで "slide_from_right" /
"fade" / "slide_from_bottom" 等を指定可能。
凝った遷移は react-native-reanimated + react-native-screens で書く(アニメーション参照)。