ナビゲーション

2026 現在の RN ナビゲーションの定番は Expo Router(ファイルベース)。 裏側は React Navigation を使っているので、深いカスタマイズは React Navigation の API に降りる。

2 つのアプローチ

Expo RouterReact Navigation 直書き
定義ファイルパス = ルートJS でツリー手書き
Deep Link自動手動設定
Web 対応○(Next.js 風の出力)
typed routes○(自動生成)手書き
新規プロジェクトこれが標準

Expo Router の基本

app/ 配下のファイル構造がそのままルート。Next.js App Router に似ている。

ファイル → URL の対応
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 /> 等のナビゲータを置く。

app/_layout.tsx
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>

app/(tabs)/_layout.tsx
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 のクエリを更新

パラメータの取得

app/posts/[id].tsx
import { useLocalSearchParams } from "expo-router"
import { Text } from "react-native"

export default function Post() {
  const { id } = useLocalSearchParams<{ id: string }>()
  return <Text>Post #{id}</Text>
}

Typed Routes(型安全な URL)

app.jsonexperiments.typedRoutes: true を有効化すると、 Linkrouter.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.jsonscheme で URL スキームを設定すると、myapp://post/123 のような URL でアプリが開く。Expo Router はそのまま該当ルートに飛ぶので、何もしなくて良い。

Universal Link / App Links(https URL)

Universal Link にすると、メールやSlackで貼った https://example.com/post/123 から直接アプリに飛べる。

状態の保持と復元

Expo Router は 戻る時の画面状態を自動で保持。アプリを完全に再起動しても URL が復元される (app.jsonscheme 設定が前提)。

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.Screenanimation オプションで "slide_from_right" / "fade" / "slide_from_bottom" 等を指定可能。
凝った遷移は react-native-reanimated + react-native-screens で書く(アニメーション参照)。