Expo Router v2 で Drawer Menu (スライドメニュー)を作る

Expo Router v2 を使ってドロワーメニューを作ります。左側から設定画面へのリンクを表示させるようなメニューです。 「Twitterのようなスライドメニュー」と紹介されることもあります。

  • “expo”: “~49.0.13”
  • “expo-router”: “^2.0.9”,
  • “@react-navigation/drawer”: “^6.6.4”

Expo Router のインストール

Expo Router V2 の始め方は以下のリンクを見てコピペするのが一番良いです。

babel.config.js の設定も含めて全部書いてます。公式を見ましょう。

React Navigation の drawer navigatorをインストール

以下のリンクのとおりに、必要なライブラリをインストールします。 babel.config.js に react-native-reanimated/plugin の設定が必要な点に注意してください。

shell
npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated
shell
npm install @react-navigation/drawer

ディレクトリ構成

以下のようなディレクトリ構成になります。

shell
app
├── (drawer)
│   ├── _layout.tsx
│   ├── home
│   │   ├── _layout.tsx
│   │   └── index.tsx
│   └── settings
│       ├── _layout.tsx
│       └── index.tsx
└── index.tsx

app/index.tsx

app/index.tsx
import { Redirect, useRootNavigationState } from 'expo-router';

export default function Page() {
  const rootNavigationState = useRootNavigationState();

  // ルートナビゲーターが準備できている場合にのみ、リダイレクトをレンダリング
  if (rootNavigationState?.key == null) return null;

  return <Redirect href={'/(drawer)/home'} />;
}

app/(drawer)/_layout.tsx

app/(drawer)/_layout.tsx
import { Drawer } from 'expo-router/drawer';

export default function DrawerLayout() {
  return <Drawer screenOptions={{ headerShown: false, swipeEdgeWidth: 0 }}></Drawer>;
}

(drawer)Groups と呼ばれる書き方です。

グループ構文()を使えば、URLでセグメントが表示されないようにすることができる。

app/root/home.jsは/root/homeにマッチします。
app/(root)/home.js は /home にマッチします。
これは、URLに追加のセグメントを追加せずにレイアウトを追加するのに便利です。グループはいくつでも追加できます。

グループは、アプリのセクションを整理するのにも適しています。以下の例では、app/(app)はメインアプリの場所、app/(aux)は補助ページの場所です。これは、メインアプリの一部である必要はないが、外部リンクしたいページを追加するのに便利です。

https://docs.expo.dev/routing/layouts/#groups

_layout.tsx について。

デフォルトでは、ルートは画面全体を埋めます。ルート間の移動は、アニメーションなしの全ページ遷移です。ネイティブアプリでは、ユーザーはヘッダーやタブバーのような共有要素がページ間で持続することを期待しています。これらはレイアウト・ルートを使って作成されます。

Expo Routerは、指定されたディレクトリに対して1つのレイアウトルートを追加することをサポートしています。

https://docs.expo.dev/routing/layouts/#create-a-layout-route

app/(drawer)/home/_layout.tsx

app/(drawer)/home/_layout.tsx
import { Stack } from 'expo-router';

export default function HomeLayout() {
  return <Stack />;
}

app/(drawer)/home/index.tsx

app/(drawer)/home/index.tsx
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import { Drawer } from 'expo-router/drawer';
import { DrawerToggleButton } from '@react-navigation/drawer';
import { Ionicons } from '@expo/vector-icons';
export default function Page() {
  return (
    <>
      <View style={styles.container}>
        <Drawer.Screen
          options={{
            title: 'Home',
            headerShown: true,
            headerLeft: () => <DrawerToggleButton />,
            headerRight: () => (
              <TouchableOpacity onPress={() => {}} style={styles.reloadButton}>
                <Ionicons name="reload" size={24} color="black" />
              </TouchableOpacity>
            ),
            headerRightContainerStyle: {
              marginRight: 18,
            },
          }}
        />
        <Text style={{ fontSize: 24 }}>Index page of Home Drawer</Text>
      </View>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  reloadButton: {
    marginRight: 17,
  },
});

app/(drawer)/settings/_layout.tsx

app/(drawer)/settings/_layout.tsx
import { Stack } from 'expo-router';

export default function HomeLayout() {
  return <Stack />;
}

app/(drawer)/settings/index.tsx

app/(drawer)/settings/index.tsx
import { Text, StyleSheet, View } from 'react-native';
import { DrawerToggleButton } from '@react-navigation/drawer';
import { Drawer } from 'expo-router/drawer';

export default function Page() {
  return (
    <View style={styles.container}>
      <Drawer.Screen
        options={{
          title: 'Settings',
          headerShown: true,
          headerLeft: () => <DrawerToggleButton />,
        }}
      />
      <Text style={{ fontSize: 24 }}>Index page of Settings Drawer</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});