React NativeのExpo Routerで設定できる基本的なナビゲーションを理解
本文書ではReact Nativeにナビゲーション(ルーティング, ページ移動, Stack)を実装する際に利用されるExpo Routerの基本的な設定方法について動作確認を行いながら説明を行なっています。
本ブログの記事の公開時ののExpo Routerのバージョン3.5で現在も活発に開発が行われています。
動作確認を行なったmacOSのバージョンはsonoma14.5, Xcodeのバージョンは15.3です。
目次
Expo Routerとは
React Nativeでナビゲーションを実装する場合にExpo Routerの他にはReact NavigationライブラリをがありますがReact Nativeの開発ツールであるExpoを利用する場合には特に理由がない限り新しいプロジェクトではExpo Routerを利用します。Expo RouterとReact Navigationは全く別のライブラリではなくExpo RouterはReact Navigationを使って作成されており、React Navigationの機能を活用してReact Navigationよりも簡単にナビゲーションの設定を行うことができます。
Expo RouterとReact Navigatigationの大きな違いはルーティングの設定です。Expo Routerではファイルシステムルーティングを採用しているのでReactのメタフレームワークNext.jsと同様にディレクトリの中にファイルを保存するとディレクトリとファイルの構造により自動でルーティングが設定されます。
手動でルーティングを設定するReact Navigationについては本サイトでも公開済みので参考にしてください。
プロジェクトの作成
Expo RouterはExpoで作成したプロジェクトで利用することができるのでプロジェクトの作成は”npx create-expo-app@latest”コマンドで行います。–templateオプションを指定することでインストール時にテンプレートを選択することができます。 –templateオプションにblankを指定しない場合はコマンド実行後にテンプレートを選択することができます。TypeScriptを利用するかどうかもテンプレートで選択することができますが本文書ではJavaScriptの”Blank”を指定しています。
% npx create-expo-app@latest --template blank
✔ What is your app named? … react-navive-expo-router
✔ Downloaded and extracted project files.
> npm install
//略
✅ Your project is ready!
To run your project, navigate to the directory and run one of the following npm commands.
- cd react-navive-expo-router
- npm run android
- npm run ios
- npm run web
プロジェクトの作成後はプロジェクトディレクトリに移動します。
% cd react-navive-expo-router
Expo Routerのインストール
プロジェクトの作成時に”Blank”templateを指定した場合はExpo Routerのインストールは行われていないのでExpo Routerに関係するライブラリのインストールを行う必要があります。
% npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar
Expo Routerのインストールが完了したら、package.jsonファイルを開いてエントリーポイントの設定を行うことができるmainの値をデフォルトの”expo/AppEntry.js”から”expo-router/entry”に変更します。
{
"name": "react-navive-expo-router",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~51.0.18",
"expo-status-bar": "~1.12.1",
"react": "18.2.0",
"react-native": "0.74.3",
"expo-router": "~3.5.17",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "3.31.1",
"expo-linking": "~6.3.1",
"expo-constants": "~16.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}
次にapp.jsonファイルにschamaプロパティを追加して任意のアプリケーションの名前を設定しておきます。ここでは”react-navive-expo-router”としています。
{
"expo": {
"name": "react-navive-expo-router",
"slug": "react-navive-expo-router",
"scheme": "react-navive-expo-router",
"version": "1.0.0",
//略
schemeを設定しない場合は”npm start”コマンドを実行した時にWARNINGが表示されます。設定しない場合は開発環境では動作しても本番環境では正常に動作しなくなる可能性があるようです。
WARN Linking requires a build-time setting `scheme` in the project's Expo config (app.config.js or app.json) for production apps, if it's left blank, your app may crash. The scheme does not apply to development in the Expo client but you should add it as soon as you start working with Linking to avoid creating a broken build. Learn more: https://docs.expo.dev/guides/linking/
警告: リンキングには、本番アプリのプロジェクトのExpo設定(app.config.jsまたはapp.json)にビルド時の設定schemeが必要です。これが空白のままだと、アプリがクラッシュする可能性があります。schemeはExpoクライアントでの開発には適用されませんが、リンキングを使用し始めるとすぐに壊れたビルドを作成しないように追加するべきです。詳細はこちらを参照してください: https://docs.expo.dev/guides/linking/
はじめてのExpo Router
インストールが完了したらデフォルトで作成されているプロジェクトディレクトリのApp.jsファイルを削除してappディレクトリを作成し、その下にindex.jsファイルを作成します。index.jsファイルを作成することで自動でルーティングが設定されます。
import { Text } from 'react-native';
export default function Page() {
return <Text>Home page</Text>;
}
“npm start”コマンドで開発サーバを起動します。Expoで構築したアプリケーションの動作確認はXcodeのSimulatorか”Expo Go”を利用して行うことができます。手元のiPhoneに”Expo Go”をインストールした場合は”npm start”コマンド実行後に表示されるQRコードをカメラにかざすとiPhone上でアプリケーションが起動します。
appディレクトリの下にさらにabout.jsファイルを作成して以下のコードを記述します。
import { Text } from 'react-native';
export default function Page() {
return <Text>About page</Text>;
}
index..js, about.jsを作成するとファイルベースルーティング機能により自動で/, /aboutのルーティングが作成されます。index.jsの内容はアプリケーションの初期画面となりますが、/aboutのルーティングが正常に作成されているか確認するためにindex.jsのページから/aboutのAboutページ(about.js)に移動できるようにLinkコンポーネントを利用します。Linkコンポーネントを利用することでページ間を移動することができます。Linkコンポーネントのhref propsにはAboutページのパスである/aboutを設定します。
import { Link } from 'expo-router';
import { Text, View } from 'react-native';
export default function Page() {
return (
<View>
<Text>Home page</Text>
<Link href="/about">About</Link>
</View>
);
}
画面上には”Home page”とaboutページへのリンクが設定された文字列Aboutが表示されます。
Aboutの文字列をクリックするとAboutページに移動することができます。
appディレクトリにファイルを作成するだけで自動でルーティングが設定され、Linkコンポーネントを利用してページの移動が行えることが確認できました。
Layout Routesの設定
index.js, about.jsファイルを使って2つのページを作成しましたが通常はアプリケーションでは各ページはコンテンツ以外にヘッダーやフッターなどで構成されそれらの要素は複数のページで共有します。Expo Routerでは共有できる要素をLayout Routesで設定することができます。
appディレクトリの下に_layout.jsファイルを作成します。ファイル名の先頭は”_”(アンダーバー)です。
import { Text } from 'react-native';
export default function Layout() {
return <Text>レイアウト</Text>;
}
設定後アプリケーションを確認するとレイアウトの文字のみ左上に表示されそれまで表示されていた”Home page”の文字列が表示されなくなります。
index.jsやabout.jsファイルで設定した内容を表示させるためLayout RouteではSlotコンポーネントを利用します。
import { Slot } from 'expo-router';
import { Text, View } from 'react-native';
export default function HomeLayout() {
return (
<View>
<Text>レイアウト</Text>
<Slot />
</View>
);
}
画面上にはindex.jsの内容が表示されます。
表示を画面に収まるようにするためにSafeAreaViewコンポーネントを利用することができます。_layout.jsファイルでViewタグをSafeAreaViewタグに変更すると画面に収まるようになります。
Layout RoutesではSlotコンポーネントを利用することでindex.js, about.jsに記述したコンテンツを表示することができ、Linkコンポーネントでページを移動することができました。
Slotコンポーネントを利用せずページ間を移動するためのナビゲーションがExpo Routerにはいくつか準備されていますがその中からモバイルアプリでの利用頻度の高いStackとTabsのNavigationについて動作確認を行います。Stack, Tabs以外のNavigationにDrawer、Modalなどがあります。
Expo Routerの基本のナビゲーションであるStack Navigationを利用しナビゲーションの設定を行います。名前にStackと入っている通り、Stackを利用してページの移動を管理します。Stackは階層構造を持ちページを移動するとStackの上に情報が追加され、元のページに戻るとStackから削除されます。Stackを利用することでページに移動したり、移動したページから戻ったりすることができます。
Stack Navigationの設定にはStackコンポーネントを利用します。appディレクトリの_layout.jsファイルで設定していたSlotをStackに置き換えます。
import { Stack } from 'expo-router';
export default function HomeLayout() {
return <Stack />
);
}
Stackに置き換えただけで画面の上部にヘッダーが表示されその中にファイルの名前に対応するindexの文字列が自動で表示されます。
表示されているページのaboutページへのリンクであるAboutの文字列をクリックしてください。
ヘッダーにはファイルに対応するaboutという文字列だけではなくindexページに戻るためのリンクも自動で表示されます。indexをクリックするとindexページに戻ります。このようにStack Navigationを利用することでページを行ったり、来たりすることが簡単にできるようになります。
画面上の上部に表示されているヘッダーの色や文字のスタイルをカスタマイズしたい場合にはpropsのscreenOptionsを利用して行うことができます。ヘッダーの背景色を”#f4511e”、文字色を白、文字の太さをBoldに設定しています。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
);
}
設定した通り、ヘッダーの背景や文字の色やスタイルが変更されることが確認できます。
ページ毎に表示されているindex, aboutの文字列(title)を変更したい場合にはStack.Screenのname propsにページの名前を指定してoption propsでtitleプロパティを設定することが変更できます。ヘッダーの色も各ページで個別設定することができます。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen name="index" options={{ title: 'ホーム' }} />
<Stack.Screen
name="about"
options={{
headerStyle: { backgroundColor: '#ddd' },
title: '私たちについて',
}}
/>
</Stack>
);
}
optionの設定は_layout.jsファイルだけではなくindex.js, about.jsファイルの中で設定することもできます。index.js, about.jsで設定を行うため_layout.jsファイルで既存Stack.Screenの設定を削除します。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
);
}
index.jsファイルにStack.Screenを追加してoptionsでtitleを設定します。設定後、_layout.jsで設定した内容と同じ内容で設定できることが確認できます。
import { Stack, Link } from 'expo-router';
import { Text, View } from 'react-native';
export default function Page() {
return (
<View>
<Stack.Screen
options={{
title: 'ホーム',
}}
/>
<Text>Home page</Text>
<Link href="/about">About</Link>
</View>
);
}
Tabsはアプリケーションの下部(フッター部分)にアイコンを表示してアイコンをクリックすることでページ移動を行うことができるナビゲーションです。Stack Navigationと同様にモバイルアプリでは必ず利用されているナビゲーションの一つです。
設定にはTabsコンポーネントを利用します。appディレクトリの_layout.jsファイルで以下のように記述します。
import { Tabs } from 'expo-router';
export default function Layout() {
return <Tabs />;
}
index.jsとabout.jsファイルは以下のとおりです。タブでページ移動が行えるのでindex.jsファイルからaboutへ移動するためのLinkコンポーネントは削除しています。
import { Text } from 'react-native';
export default function Page() {
return <Text>Home page</Text>;
}
import { Text } from 'react-native';
export default function Page() {
return <Text>About page</Text>;
}
設定後は画面上のフッター部分にindex, aboutのボタンが表示され、クリックするとページに移動することができます。
画面の下部を拡大すると下記のように表示されます。
ヘッダーのtitleは_layout.jsファイルのTabs.Screenで設定することができます。
import { Tabs } from 'expo-router';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'ホーム' }} />
<Tabs.Screen name="about" options={{ title: '私たちについて' }} />
</Tabs>
);
}
タイトルを更新すると下部のTabの文字も一緒に更新されます。
アイコンの設定
デフォルトでは画面で確認した通り逆三角の記号が表示されています。アイコンを設定したい場合にはoptionsのtabBarIconで設定することができます。アイコンは追加インストールなしに@expo/vector-iconsからimportして利用することができます。
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: 'ホーム',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen name="about" options={{ title: '私たちについて' }} />
</Tabs>
);
}
逆三角からホームのアイコンに変わっていることが確認できます。
StackとTabs Navigationはアプリケーション内でどちらか一方のみ設定できるというわけではなく一緒に利用することができます。
StackとTabsを一緒に設定した場合の動作確認を行います。Stack Navigationで設定するページの一つにTabs Navigationを設定していきます。Tabs Navigationを追加するためにappの_layout.jsファイルではindex(index.jsに対応), about(about.jsに対応)の設定に加えて(tabs)を設定しています。()はGroup Syntaxと呼ばれグループ化することができるので(tabs)ディレクトリを作成してその下に_layout.js, index.js, profile.jsファイルを作成することで(tabs)ディレクトリのみに適用されるLayoutを設定することができます。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="about" />
<Stack.Screen name="(tabs)" />
</Stack>
);
}
作成した(tabs)ディレクトリ下のファイルを利用してTab Navigationを設定することができます。(tabs)ディレクトリの_layout.jsファイルでTabsコンポーネントを設定します。
import { Tabs } from 'expo-router';
export default function Layout() {
return <Tabs />;
}
(tabs)ディレクトリの下のindex.js, profile.jsファイルには以下のコードを記述します。
import { Text } from 'react-native';
export default function Page() {
return <Text>Tabs Index page</Text>;
}
import { Text } from 'react-native';
export default function Page() {
return <Text>Profile page</Text>;
}
(tabs)ディレクトリに作成したindex.js, profile.jsは_layout.jsファイルでTabsコンポーネントを設定しているので画面のフッターにTabとして表示されます。
/app/index.jsから(tabs)のページにアクセスるためにはhrefに(tabs)を設定することができます。
import { Link } from 'expo-router';
import { Text, View } from 'react-native';
export default function Page() {
return (
<View>
<Text>Home page</Text>
<Link href="/about">About</Link>
<Link href="(tabs)">Tabs</Link>
</View>
);
}
画面上にTabsとAboutへのリンクを持つ文字列が表示されます。
Tabsをクリックすると以下のようにヘッダーには(tabs)、indexの文字列が2段になって表示されます。フッターには(tabs)ディレクトリ内にあるindex.js, profileのタブのみ表示されます。app/_layout.jsで設定しているStack.Screenのaboutが表示されることはありません。
ヘッダーを拡大するとStack Navigationの一部でもあるのでヘッダーにはクリックを行ったindexページへのリンクが表示されています。
フッターにはindex, profileのタブのみ表示されています。
表示されているヘッダーのタイトルの(tabs)を非表示にするために下記のようにapp/_layout.jsのStack.ScreenのoptionsでheaderShownオプションを利用します。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="about" />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
);
}
ヘッダーから(tabs)の文字列とindexページに戻るためのリンクが非表示となります。
ヘッダーのタイトルのindexを変更したい場合にはapp/(tabs)/_layout.jsファイルで行うことができます。
import { Tabs } from 'expo-router';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'タブホーム' }} />
<Tabs.Screen name="profile" />
</Tabs>
);
}
indexからoptionsのtitleプロパティで設定した”タブホーム”に変更されていることが確認できます。
フッターを見るとヘッダーのtitleだけではなくTabの文字列も一緒に更新されます。
Stack+Tabsの2つを利用したNavigation設定を確認することができました。
先ほどはStack Navigationの下にTabs Navigationを設定しましたがここではその逆のTabs Navigationの下にStack Navigationの設定ができることを確認します。
(tabs)のディレクトリ名を(stacks)に変更してapp/_layout.jsファイルをStack NavigationからTabs Navigationの設定に変更します。
import { Tabs } from 'expo-router';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" />
<Tabs.Screen name="about" />
<Tabs.Screen name="(stacks)" />
</Tabs>
);
}
(stacks)ディレクトリのindex.jsとprofile.jsは以下のとおりです。
import { Text, View } from 'react-native';
export default function Page() {
return <Text>Stack Index page</Text>;
}
import { Text } from 'react-native';
export default function Page() {
return <Text>Profile page</Text>;
}
(stacks)ディレクトリの_layout.jsをTabsコンポーネントからStackコンポーネントに変更します。
import { Stack } from 'expo-router';
export default function Layout() {
return <Stack />;
}
アプリケーションの画面を確認するとフッターには3つのTabが表示されます。1つのTabは(stacks)となっています。
(stacks)のタブをクリックすると下記の画面に表示されます。
ヘッダー部分を拡大して確認すると以下のように(stacks)とindexが表示されます。
Tabの(stacks)を”スタック”の文字列に変更するためにapp/_layout.jsファイルの(stacks)のTabs.Screenのoptionsでtitleを変更します。
import { Tabs } from 'expo-router';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" />
<Tabs.Screen name="about" />
<Tabs.Screen name="(stacks)" options={{ title: 'スタック' }} />
</Tabs>
);
}
ヘッダーだけではなくTabsの文字列も(stacks)から変更した”スタック”に更新されていることが確認できます。
ヘッダーのスタックを非表示にするためにheaderShownプロパティをfalseにします。
import { Tabs } from 'expo-router';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" />
<Tabs.Screen name="about" />
<Tabs.Screen
name="(stacks)"
options={{ title: 'スタック', headerShown: false }}
/>
</Tabs>
);
}
表示されていたヘッダーのスタックが非表示となります。
フッターに表示されているTabのスタックはそのままです。
ヘッダーのindexを変更したい場合にはapp/(stacks)/_layout.jsファイルで設定します。
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack>
<Stack.Screen name="index" options={{ title: 'スタックホーム' }} />
<Stack.Screen name="profile" options={{ title: 'プロファイル' }} />
</Stack>
);
}
indexからoptionsのtitleプロパティで設定した”スタックホーム”に変更されます。
/app/(stacks)/index.jsの”スタックホーム”ページから”プロファイル”ページへの移動を確認します。
import { Link } from 'expo-router';
import { Text, View } from 'react-native';
export default function Page() {
return (
<View>
<Text>Stack Index page</Text>
<Link href="/(stacks)/profile">Profile</Link>
</View>
);
}
Stack Navigationで設定されているのでindexページ(タブホーム)からprofileページに移動するとヘッダーには移動元の”スタックホーム”のリンクが表示されます。
Tabs+Stackの2つを利用したNavigation設定を確認することができました。
Hooksによるページの移動
Linkコンポーネントを利用してページの移動を行っていましたがHookを利用してページ移動を行うことができます。
app/index.jsからapp/about.jsの移動をuseNavigation Hookを利用して行うことができます。
import { useNavigation } from 'expo-router';
import { Text, View } from 'react-native';
export default function Page() {
const navigation = useNavigation();
return (
<View>
<Text>Home page</Text>
<Text
onPress={() => {
navigation.navigate('about');
}}
>
About Page
</Text>
</View>
);
}
useRouter Hook
useNavigation Hookと同様にuseRouter Hookを利用してページを移動することができます。
import { useRouter } from 'expo-router';
import { Text, View } from 'react-native';
export default function Page() {
const router = useRouter();
return (
<View>
<Text>Home page</Text>
<Text
onPress={() => {
router.push('/about');
}}
>
About Page
</Text>
</View>
);
}
routerオプジェクトにはそのほかにnavigate, replace, back, canGoBack, setParamsメソッドがあります。
ここまでの動作確認でExpo Routerの基本的な動作を確認することができました。