React Nativeを利用してモバイルアプリケーションの開発を行うと必ず画面間の移動が必要になります。React Nativeのみではその機能を持っておらず追加のライブラリのインストールが必要となります。本文書ではReact Navigationを利用して画面間の移動の動作確認を行っています。シンプルなコードのみ利用しているので本文書を通してReact Navigationの設定方法の基本を理解することができます。対象はReact Nativeの学習を開始し始めた人です。

macOSを利用し、Reat Nativeの開発ツールにExpoを利用しReact Navigationのバージョンは7.Xをインストールしています。

2024年7月に最新情報にリライトしています。リライト時はバージョン7のRC(Release Candidate)のためライブラリをインストールする際に@nextがついています。
fukidashi

ファイルシステムベースルーティングが利用できるExpo Routerを利用することでReact Navigationよりも簡単にナビテーションを設定することができます。特に理由がない限り新しいプロジェクトではこちらを利用するのがおすすめです。

React Nativeの入門書の方であれば先に下記の文書を読んでもらうことをお勧めします。ExpoによるReact Nativeの開発環境の構築には下記の文書の前半に記述しているので参照してください。

動作確認を行なったmacOSのバージョンはsonoma14.5, Xcodeのバージョンは15.3です。

環境を構築する

本文書ではmacOSの環境にNode.jsがインストール済み, XCodeのインストールまたは手元のモバイル機器に”Expo Go”がインストールされている状態から開始しています。

プロジェクトの作成には”npx create-expo-app@latest –template”コマンドを実行します。実行するとテンプレートを選択することができるのでここでは”Blank”を選択しています。またプロジェクト名には任意のreact-native-navigationという名前をつけています。


 % npx create-expo-app@latest --template
✔ Choose a template: › Blank
✔ What is your app named? … react-native-navigation
✔ 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-native-navigation
- npm run android
- npm run ios
- npm run web

プロジェクトの作成が完了後、プロジェクトディレクトリに移動して開発サーバを起動するためにnpm startコマンドを実行します。”Expo Go”アプリをiPhoneデバイスにインストールしている場合は表示されいているQRコードをカメラにかざすとExpo Goが起動してアプリ画面が表示されます。iOSのSimulatorを利用する場合はキーボードの”i”を押してください。


 % npm start

> react-native-navigation@1.0.0 start
> expo start

Starting project at /Users/mac/Desktop/react-native-navigation
Starting Metro Bundler

//ここにQRコード表示

› Metro waiting on exp://192.168.2.200:8081
› Scan the QR code above with Expo Go (Android) or the Camera app (iOS)

› Using Expo Go
› Press s │ switch to development build

› Press a │ open Android
› Press i │ open iOS simulator
› Press w │ open web

› Press j │ open debugger
› Press r │ reload app
› Press m │ toggle menu
› Press o │ open project code in your editor

› Press ? │ show all commands

Logs for your project will appear below. Press Ctrl+C to exit.

下記はiOSのSimulatorですが下記の画面が表示されれば正常に動作しています。

iOSシュミレーターで確認
iOSシュミレーターで確認

React Navigatorの設定

React Navigatorインストールと初期設定についてはReactct Navigatorのドキュメントを参考にして行っていきます。

ライブラリのインストール

React Navigatorの利用するためには1つのライブラリではなく複数のライブラリのインストールが必要になります。最初にreact-navigation/nativeをインストールします。


% npm install @react-navigation/native@next

次に本文書ではExpoを利用しているのでexpoコマンドを利用してreact-native-screens, react-native-safe-area-contextをインストールします。


% npx expo install react-native-screens react-native-safe-area-context

package.jsonで各ライブラリのバージョンを確認しておきます。


{
  "name": "react-native-navigation",
  "version": "1.0.0",
  "main": "expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@react-navigation/native": "^7.0.0-rc.4",
    "expo": "~51.0.18",
    "expo-status-bar": "~1.12.1",
    "react": "18.2.0",
    "react-native": "0.74.3",
    "react-native-screens": "3.31.1",
    "react-native-safe-area-context": "4.10.1"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0"
  },
  "private": true
}

React Navigatorについて

React Navigatorではナビゲーション(画面の移動)する方法として大きく3つ(Stack, Tabs, Drawer)あります。その3つもさらに細かく分けれており5つのライブラリ(Stack, Native Stack, Drawer, Bottom Tabs,Material Top Tabs)に分かれています。どのナビゲーターを利用するかによってインストールするライブラリが異なります。アプリ内で複数のナビゲーションを利用することも可能です。またバージョン7からNavigationの設定方法が2つになり、バージョン7で追加された方法がStatic Configurationと呼ばれ、これまでの方法がDynamic configurationと呼ばれます。これからReact Navigationを使い始める人はStatic Configurationを利用することが推奨されています。

Native Stackでは各OSのNativeのナビゲーションシステムを利用して画面の移動を行います。Stackは各OSのNativeのナビゲーションシステムを利用しておらずJavaScriptを利用して画面の移動を実現しています。そのためカスタマイズが可能になっています。パフォーマンスに関してはNativeのナビゲーションシステムを利用しているNative Stackのほうが良いとドキュメントには記載されています。
fukidashi

Native Stack Navigator(Dynamic)の設定

React Navigationのドキュメントに沿っていくとNative Stack Navigatorの設定を解説しているのでまずはNative Stack Navigatorのインストールを行います。


% npm install @react-navigation/native-stack@next

ここまででナビゲーションを実装するために必要なライブラリのインストールが完了したので画面の作成を行います。

プロジェクトフォルダにscreensを作成してHomeScreen.jsを作成します。


import { View, Text } from 'react-native';

const HomeScreen = () => {
  return (
    <View>
      <Text>ホーム画面</Text>
    </View>
  );
};

export default HomeScreen;
React Navigationによる画面の移動に注目しているの本文書ではstyleの設定は行いません。
fukidashi

App.jsファイルでNavigationを設定します。Navigationの設定にはバージョン7から登場したStaticとこれまでに利用していたDynamicの2つの方法があります。最初はDynamicでの設定を行います。

react-navigation/nativeからimportするNavigationContainerはどのナビデーションでも利用する必須のコンポーネントです。native-stackからimprortしたcreateNativeStackNavigatorを利用してStackを作成し設定を行っています。


import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Stack.Screenでは作成したコンポーネントのHomeScreenを指定し、name propsで名前をHomeとしています。ここまでの設定でiOS simulatorを確認すると下記のように表示されます。上部のHomeはStack.Screenタグのname propsで指定したHomeです。画面の上部にヘッダーが自動で付与され画面の名前が表示されます。

ホーム画面の作成
ホーム画面の作成

画面間の移動を行うために新たにscreensフォルダにUserScreen.jsファイルを作成します。


import { View, Text } from 'react-native';

const UserScreen = () => {
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

export default UserScreen;

追加したUserScreen.jsファイルをApp.jsファイルに追加します。


import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import UserScreen from './screens/UserScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="User" component={UserScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

ページ間の移動設定を行っていないためUserScreenコンポーネントを追加しても何も変化はありません。

UserScreenコンポーネントを確認したい場合はinitialRouteName prpsをStack.Navigatorコンポーネントで設定することで最初に表示する画面を指定することができます。initialRouteNameをUserに変更することでUser画面が表示されることを確認してください。initialRouteName prpsを設定しないデフォルトの状態では一番上のHomeが表示されることになります。


<NavigationContainer>
  <Stack.Navigator initialRouteName="Home">
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="User" component={UserScreen} />
  </Stack.Navigator>
</NavigationContainer>

画面の移動

Home画面からUser画面に移動できるようにHomeScreenコンポーネントにButtonを追加します。


import { View, Text, Button } from 'react-native';

const HomeScreen = () => {
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="ユーザ" />
    </View>
  );
};

export default HomeScreen;

Home画面にはユーザの文字が表示されます。ユーザの文字列をタッチしても変化はありません。

ボタンの表示
ボタンの表示

ユーザボタンをタッチするとUser画面に移動できるように設定を行います。設定を行う前にStack.Screenの設定したコンポーネントに渡されるpropsを確認します。

HomeScreenにはpropsでは以下のオブジェクトが渡されます。


import { View, Text, Button } from 'react-native';

const HomeScreen = (props) => {
  console.log(props)
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="ユーザ" />
    </View>
  );
};

export default HomeScreen;

コンソールに表示されたpropsの内容を確認するとオブジェクトの中にnavigationとrouteオブジェクトが含まれいていることが確認できます。


Object {
  "navigation": Object {
    "addListener": [Function addListener],
    "canGoBack": [Function canGoBack],
    "dispatch": [Function dispatch],
    "getParent": [Function getParent],
    "getState": [Function anonymous],
    "goBack": [Function anonymous],
    "isFocused": [Function isFocused],
    "navigate": [Function anonymous],
    "pop": [Function anonymous],
    "popToTop": [Function anonymous],
    "push": [Function anonymous],
    "removeListener": [Function removeListener],
    "replace": [Function anonymous],
    "reset": [Function anonymous],
    "setOptions": [Function setOptions],
    "setParams": [Function anonymous],
  },
  "route": Object {
    "key": "Home-hkQU4HYZjf3AoEmIFgDqF",
    "name": "Home",
    "params": undefined,
  },
}

2つのオブジェクトの中の1つであるnavigationのnavigate関数を利用します。ButtonにonPressイベントを設定しボタンをタッチするとnavigate関数が実行されます。navigate関数の引数にはStack.Screenのname propsで設定した”User”を指定することができます。


import { View, Text, Button } from 'react-native';

const HomeScreen = ({ navigation }) => {
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="ユーザ" onPress={() => navigation.navigate('User')} />
    </View>
  );
};

export default HomeScreen;

onPressイベントを設定後にユーザボタンをタッチするとUser画面に移動することができます。User画面の上部にはHomeへ戻るためのボタンが表示されておりタッチするとHome画面に戻ることができます。画面を行き来することができるようになります。

ユーザ画面への移動
ユーザ画面への移動

ここまでの設定で複数の画面間の移動を実装することができました。

Screenにパラメーターを渡す

HomeScreenコンポーネントに渡されるpropsを再度確認するとnavigationオブジェクトの中にはnavigate以外にもpop, push, goBackなどの関数があることがわかります。またrouteオブジェクトの中にはnameやparamsを確認することができます。

navigate関数だけでなく、pop ,push, goBack関数によって画面の移動を行うことができます。
fukidashi

Object {
  "navigation": Object {
    "addListener": [Function addListener],
    "canGoBack": [Function canGoBack],
    "dispatch": [Function dispatch],
    "getParent": [Function getParent],
    "getState": [Function anonymous],
    "goBack": [Function anonymous],
    "isFocused": [Function isFocused],
    "navigate": [Function anonymous],
    "pop": [Function anonymous],
    "popToTop": [Function anonymous],
    "push": [Function anonymous],
    "removeListener": [Function removeListener],
    "replace": [Function anonymous],
    "reset": [Function anonymous],
    "setOptions": [Function setOptions],
    "setParams": [Function anonymous],
  },
  "route": Object {
    "key": "Home-hkQU4HYZjf3AoEmIFgDqF",
    "name": "Home",
    "params": undefined,
  },
}

パラメータを渡す場合はnavigate関数で指定したScreenの名前の後にオブジェクトで設定します。


const HomeScreen = ({ navigation }) => {
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button
        title="ユーザ"
        onPress={() =>
          navigation.navigate('User', {
            userId: 1,
          })
        }
      />
    </View>
  );
};

UserScreen.jsでrouteの値をconsole.logでコンソールに出力して確認します。


const UserScreen = ({ route }) => {
  console.log(route);
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

navigte関数で設定したオブジェクトがparamsの中に入っていることがわかります。route.params.userIdでUserScreenコンポーネントで値を取得することができます。


Object {
  "key": "User-kzKLb1Gqybs5USRBEc6X5",
  "name": "User",
  "params": Object {
    "userId": 1,
  },
  "path": undefined,
}

複数の値を渡したい場合はオブジェクトのプロパティを増やすことで実現できます。

Headerの設定

Screenの上部にはHomeまたはUserといったname propsで設定した値が表示されていました。nameの値の変更はname propsの値を変更することでもできますがoptions propsを利用することで変更が可能です。


export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen
          name="User"
          component={UserScreen}
          options={{ title: 'ユーザ画面' }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

設定後User画面を表示するとヘッダーの文字がoptionsのtitleで設定したユーザ画面に変更されることが確認できます。

ヘッダーのタイトル変更
ヘッダーのタイトル変更

先ほど確認したパラメータを使ってタイトルを設定することもできます。


<Button
  title="ユーザ"
  onPress={() =>
    navigation.navigate('User', {
      userId: 1,
    })
  }
/>

optionsの中では関数を利用してtitleを設定することができます。


export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen
          name="User"
          component={UserScreen}
          options={({ route }) => ({
            title: `ユーザID${route.params.userId}の画面`,
          })}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
パラメータを利用したタイトルの設定
パラメータを利用したタイトルの設定

optionsはrouteだけではなくnavigationのオブジェクトを渡すこともできます。

ヘッダー自体を非表示にしたい場合はoptionsでheaderShownをfalseにすることで実現できます。


<Stack.Screen
  name="User"
  component={UserScreen}
  options={{ headerShown: false }}
/>

ライフサイクルフックの設定

Reactではコンポーネントのマウント時、アンマウント時に何か処理を行いたい場合にuseEffectを利用することができます。画面の移動を行なった時にReactと同様にReact NativeでもuseEffectを利用することができるのか確認していきます。

初期画面のHomeScreen.jsと移動先の画面UserScreen.jsどちらにもuseEffectを設定して動作を確認します。


import { useEffect } from 'react';
import { View, Text, Button } from 'react-native';

const HomeScreen = ({ navigation }) => {
  useEffect(() => {
    console.log('Home Mount');
    return () => {
      console.log('Home Unmount');
    };
  }, []);
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button
        title="ユーザ"
        onPress={() =>
          navigation.navigate('User', {
            userId: 1,
          })
        }
      />
    </View>
  );
};

export default HomeScreen;

import { useEffect } from 'react';
import { View, Text } from 'react-native';

const UserScreen = () => {
  useEffect(() => {
    console.log('User Mount');
    return () => {
      console.log('User Unmount');
    };
  }, []);
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

export default UserScreen;

Home画面を開くと”Home Mount”、その後User画面に移動すると”Home Unmount”が表示され、”User Mount”が表示されると予想できるかと思います。

しかし実際に動作確認を行うと”Home Mount”は表示されますがUser画面に移動しても”Home Unmount”は表示されることはなく”User Mount”が表示されます。Unmountは表示されないものなのではと考えてしまいますがUser画面からHome画面に戻ると”User Unmount”が表示されます。しかし再度”Home Mount”が表示されることはありません。その後はHome画面とUser画面の移動を繰り返しても”User Mount”と”User Unmount”しか表示されることはありません。

理由はReact Navigationの画面の移動にあります。Native Stack Navigatorという名前の通り画面はStackつまり積み重ねによりHome画面からUser画面に移動する際、User画面がHome画面の上に積み重なることで表示されています。Home画面が消えてUser画面が現れるものではありません。そのためUser画面に移動した際はHome画面の上にマウントされHomeに戻る際にはUser画面はアンマウントされます。Homeは一番したの層なので初回表示される際はマウントされますがその後はアンマウントされることはありません。

ここまでの説明でReact Native + React NavigationでのuseEffectの動作が理解できたかと思います。ではHomeScreenでも画面の移動時にある処理を実行したい場合はどのようにすればいいのでしょう?React NativeにはuseFocusEffectというHookが用意されています。useEffectからuseFocusEffectに変更して動作を確認してみましょう。

useFocusEffectはreact-navigation/nativeからimportして利用します。


import { View, Text, Button } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';

const HomeScreen = ({ navigation }) => {
  useFocusEffect(
    React.useCallback(() => {
      console.log('Home Focus');

      return () => {
        console.log('Home UnFocus');
      };
    }, [])
  );
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button
        title="ユーザ"
        onPress={() =>
          navigation.navigate('User', {
            userId: 1,
          })
        }
      />
    </View>
  );
};

export default HomeScreen;

import { View, Text } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';

const UserScreen = () => {
  useFocusEffect(
    React.useCallback(() => {
      console.log('User Focus');

      return () => {
        console.log('User UnFocus');
      };
    }, [])
  );
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

export default UserScreen;

動作確認を行うとHome画面からUser画面に移動した際には”Home UnFocus”が表示され、User画面からHome画面から戻った時には”Home Focus”が表示されます。

Native Stack Navigator(Static)の設定

バージョン7から登場したStaticな方法でのNavigationの方法を確認します。

Static NavitagionではcreateNativeStackNavigatorを利用して先にNavigationの設定を記述します。


import { createStaticNavigation } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeScreen from './screens/HomeScreen';
import UserScreen from './screens/UserScreen';

const RootStack = createNativeStackNavigator({
  screens: {
    Home: HomeScreen,
    User: UserScreen,
  },
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
  return <Navigation />;
}

HomeScreen.jsとUserScreen.jsにはDynamic Navigationで利用したコードを記述します。


import { View, Text } from 'react-native';

const HomeScreen = () => {
  return (
    <View>
      <Text>ホーム画面</Text>
    </View>
  );
};

export default HomeScreen;

import { View, Text } from 'react-native';

const UserScreen = () => {
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

export default UserScreen;

iOS simulatorで確認するとホーム画面が表示されます。最初の表示されるページを設定したい場合にはinitialRouteNameを設定することができ、”User”を設定するとUser画面が表示されます。


const Stack = createNativeStackNavigator({
  initialRouteName: 'User',
  screens: {
    Home: HomeScreen,
    User: UserScreen,
  },
});

画面の移動

画面の移動ではuseNavigation Hookを利用することができます。


import { useNavigation } from '@react-navigation/native';
import { View, Text, Button } from 'react-native';

const HomeScreen = () => {
  const navigation = useNavigation();
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="ユーザ" onPress={() => navigation.navigate('User')} />
    </View>
  );
};

export default HomeScreen;

HomeScreenのpropsを確認してuseNavitagion Hookではなくpropsに含まれるnavitaionオブジェクトが利用されるか確認します。


const HomeScreen = (props) => {
  console.log(props);
  const navigation = useNavigation();
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="ユーザ" onPress={() => navigation.navigate('User')} />
    </View>
  );
};

コードを実行するとpropsに含まれるnavigationオブジェクトがなくなり、routeオブジェクトのみになっていることが確認できます。


 {"route": {"key": "Home-s0HSB-tIncOsef_oiROw9", "name": "Home", "params": undefined}}

Screenにパラメーターを渡す

パラメータを渡す場合はnavigate関数で指定したScreenの名前の後にオブジェクトで設定します。


/略
const HomeScreen = (props) => {
  const navigation = useNavigation();
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button
        title="ユーザ"
        onPress={() =>
          navigation.navigate('User', {
            userId: 1,
          })
        }
      />
    </View>
  );
};
/略

先ほどHomeScreenコンポーネントのpropsのrouteオブジェクトにparamsが含まれていたのでそこから渡されたパラメータを受け取ることができます。


//略
const UserScreen = ({ route }) => {
  console.log(route);
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};
//略

paramsには渡されたuserId:1が含まれていることが確認できます。


 LOG  {"key": "User-hu2489KzlAAVkvRB0WWiG", "name": "User", "params": {"userId": 1}, "path": undefined}

Headerの設定

Headerの設定は下記のように行うことができます。


//略
const Stack = createNativeStackNavigator({
  initialRouteName: 'Home',
  screens: {
    Home: HomeScreen,
    User: {
      screen: UserScreen,
      options: {
        title: 'ユーザ',
      },
    },
  },
});
//略

Tab Navigationの設定

Native Stack Navigatorの動作確認を行いReact Navigatorライブラリでの画面移動をどのように行うか理解が深まったと思うので次はタブを利用した画面移動の動作確認を行なっていきます。

Tab Navigationを利用するためには別のライブラリreact-navigation/bottom-tabsをインストールする必要があります。


 % npm install @react-navigation/bottom-tabs@next

Native Stack NavigatorではNavigationを設定するためにApp.jsでcreateNativeStackNavigatorを利用していましたがTab NavigationではcreateBottomTabNavigatorを利用します。Stack.XXXタグをTab.XXXタグに変更します。


import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import HomeScreen from './screens/HomeScreen';
import UserScreen from './screens/UserScreen';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="User" component={UserScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

HomeScreen.jsとUserScreen.jsは下記のように記述します。


import { View, Text } from 'react-native';

const HomeScreen = () => {
  return (
    <View>
      <Text>ホーム画面</Text>
    </View>
  );
};

export default HomeScreen;

import { View, Text } from 'react-native';

const UserScreen = () => {
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

export default UserScreen;

ライブラリの名前にbottom-tabsとあったようにシュミレーターで確認すると下部(Bottom)にApp.jsで設定したHomeとNameのタブボタンが表示されます。

下部にボタンが表示される
下部にボタンが表示される

現在開いている画面のボタンに色がついているのでUserボタンをタッチしてください。タッチするとUser画面が表示されます。

Tabアイコンの変更

デフォルトではタブにアイコンが表示されていますがどちらも同じ形で四角に?が入っているものです。optionsでtaBarIconを設定することでアイコンを変更することができます。

アイコンの設定には@expo/vector-iconsを利用します。Expoではデフォルトから利用することができるため追加のライブラリのインストールは必要ありません。tabBarIconでは関数を利用して設定します。color, sizeが渡されるのでIoniconsではそれらの値を利用してサイズと色を設定しています。


import { Ionicons } from '@expo/vector-icons';
//略
<Tab.Screen
  name="Home"
  component={HomeScreen}
  options={{
    tabBarIcon: ({ color, size }) => (
      <Ionicons name="home" size={size} color={color} />
    ),
  }}
/>

tabBarIconを設定するとHome側のアイコンが変更されていることが確認できます。

Homeアイコンを設定
Homeアイコンを設定

User側のアイコンも更新します。


<Tab.Screen
  name="User"
  component={UserScreen}
  options={{
    tabBarIcon: ({ color, size }) => (
      <Ionicons name="person" size={size} color={color} />
    ),
  }}
/>

Headerの設定

Native Stack Navigatorの時と同様にoptionsのtitleを利用して画面上部に表示されているヘッダーのタイトルを変更することができます。


<NavigationContainer>
  <Tab.Navigator>
    <Tab.Screen
      name="Home"
      component={HomeScreen}
      options={{
        title: 'ホーム画面',
        tabBarIcon: ({ color, size }) => (
          <Ionicons name="home" size={size} color={color} />
        ),
      }}
    />
    <Tab.Screen
      name="User"
      component={UserScreen}
      options={{
        title: 'ユーザ画面',
        tabBarIcon: ({ color, size }) => (
          <Ionicons name="person" size={size} color={color} />
        ),
      }}
    />
  </Tab.Navigator>
</NavigationContainer>

titleを設定することでヘッダーのタイトルだけではなくタブボタンのタイトルも変更されることが確認できます。

ヘッダーとタブボタンのタイトル変更
ヘッダーとタブボタンのタイトル変更

アイコンの色を設定した場合はscreenOptionsで設定を行うことができます。tabBarActiveTintColorは表示されている画面の色、tabBarInactiveTintColorは表示されていない画面の色です。


export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={() => ({
          tabBarActiveTintColor: 'tomato',
          tabBarInactiveTintColor: 'gray',
        })}
      >
      //略
アイコンの色変更
アイコンの色変更

画面の移動

Tabs Navigationでは画面下部にあるタブボタンをタッチすることで画面を移動することができました。navigation.navigate関数を利用して追加したボタンをタッチすることで画面を移動することができるのか確認します。パラメーターも一緒に設定を行います。


import { View, Text, Button } from 'react-native';

const HomeScreen = ({ navigation }) => {
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button
        title="ユーザ"
        onPress={() =>
          navigation.navigate('User', {
            userId: 1,
          })
        }
      />
    </View>
  );
};

export default HomeScreen;

設定をするとUser画面の移動を行うことができます。User側でrouteオブジェクトからパラメーターを取得して表示することができます。


const UserScreen = ({ route }) => {
  return (
    <View>
      <Text>ユーザ画面{route.params.userId}</Text>
    </View>
  );
};

しかしタブボタンを利用してUser画面に移動しようとするとroute.params.userIdの値は持っていないのでコンソールには”TypeError: undefined is not an object (evaluating ‘route.params.userId’)”が表示されます。

Drawer Navigationの設定

Drawer Navigationを利用したい場合には@react-navigation/drawerライブラリをインストールする必要があります。


 % npm install @react-navigation/drawer

そのほかにreact-native-gesture-handler, react-native-reanimatedライブラリのインストールを行います。


 % npx expo install react-native-gesture-handler react-native-reanimated

babel.config.jsファイルでReanimated Babel Pluginを追加します。


module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-reanimated/plugin'],
  };
};

Drawer Navigationの場合はcreateDrawerNavigatorをimportして利用します。


import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeScreen from './screens/HomeScreen';
import UserScreen from './screens/UserScreen';

const Drawer = createDrawerNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator>
        <Drawer.Screen name="Home" component={HomeScreen} />
        <Drawer.Screen name="User" component={UserScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

シュミレーターを確認すると画面左上にメニューが表示されます。

Drawerメニューのアイコン表示
Drawerメニューのアイコン表示

メニューをクリックするとDrawer.Screenで設定したnameのHome, Userが表示されます。メニューを非表示にしたい場合は右側に表示されている背景部分をタッチすることでメニューが非表示になります。

メニューをタッチするだけではなく画面左側から右側にフリック(画面左端から右に向けて指をスライドさせる)するとメニューが表示されます。

メニューが開いた場合
メニューが開いた場合

メニューに表示されている名前をタッチすることで画面の移動を行うことができます。

メニューの名前変更

メニューの名前を変更したい場合はoptionsのtitleを設定することで変更することができます。


<NavigationContainer>
  <Drawer.Navigator>
    <Drawer.Screen
      name="Home"
      component={HomeScreen}
      options={{ title: 'ホーム画面' }}
    />
    <Drawer.Screen
      name="User"
      component={UserScreen}
      options={{ title: 'ユーザ画面' }}
    />
  </Drawer.Navigator>
</NavigationContainer>
メニューの名前の変更
メニューの名前の変更

Stack Navigatorの設定

Stack NavigatorにはOSが元々持っているナビゲーションを利用したNative Stack NavigatorとJavaScriptを利用してナビゲーションを実装したStack Navigatorがあります。ここではStack Navigatorの設定方法を確認します。Native Stack Navigatorとは異なるライブラリreact-navigation/stackをインストールします。


 % npm install @react-navigation/stack@next

さらにreact-native-gesture-handlerもインストールする必要があります。


 % npx expo install react-native-gesture-handler

Stack Navigationの場合はcreateStackNavigatorをimportして利用します。またインストールしたreact-native-gesture-handlerもimportします。


import 'react-native-gesture-handler';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from './screens/HomeScreen';
import UserScreen from './screens/UserScreen';

const Stack = createStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="User" component={UserScreen} />
        {/* <Stack.Screen name="Setting" component={SettingScreen} /> */}
      </Stack.Navigator>
    </NavigationContainer>
  );
}

NavigationContainerなどの記述はNative Stack Navigatorと同じでNative Stack Navigatorと同様に画面間を移動することができます。


import { View, Text, Button } from 'react-native';

const HomeScreen = ({ navigation }) => {
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="ユーザ" onPress={() => navigation.navigate('User')} />
    </View>
  );
};

export default HomeScreen;

import { View, Text } from 'react-native';

const UserScreen = () => {
  return (
    <View>
      <Text>ユーザ画面</Text>
    </View>
  );
};

export default UserScreen;

ここまでの動作確認で4つのNavigator(Native Stack, Tabs, Drawer, Stack)の基本設定方法を理解することができました。

Navigatorのネスト化

Native stack, Tabs, Drawerに関するNavigatorの設定を個別に行なってきましたが一緒に利用することはできるのだろうか?という疑問が湧いてくるかと思います。答えは”Yes”で、Navigatorをネスト化することで同時に利用することができます。ネスト化するというのが最初は難しいかもしれませんが実際にシンプルなコードを使って説明を行うとネスト化の意味が理解できるようになります。

Native Stack + Tabs Navigator

最初にNative Stack Navigatorの中にTabs Navigatorをネスト化した時の動作確認を行います。複数の画面が存在したほうがわかりやすいので新たにFeedScreen.js, MessageScreen.js, SettingScreen.jsの3つのコンポーネントを追加します。

作成する各ファイルの中身はUserScreen.jsを複製し、コンポーネント名とTextタグの中に入れる文字列のみ変更を行います。MessageScreen.jsの場合は下記のようになります。


import { View, Text } from 'react-native';

const MessageScreen = () => {
  return (
    <View>
      <Text>メッセージ画面</Text>
    </View>
  );
};

export default MessageScreen;

FeedScreen.js, SettingScree.jsファイルも同じように作成してください。

App.jsファイルでNative Stack Navigatorの設定を行います。Homeに関してはTabs Navigatorのために利用するため区別できるようにファイル名とコンポーネント名をHomeScreenからHomeTabsに変更しています。


import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeTabs from './screens/HomeTabs';
import UserScreen from './screens/UserScreen';
import SettingScreen from './screens/SettingScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeTab} />
        <Stack.Screen name="User" component={UserScreen} />
        <Stack.Screen name="Setting" component={SettingScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

HomeTabsコンポーネントの中でTabs Navigaorを設定します。


import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import MessageScreen from './MessageScreen';
import FeedScreen from './FeedScreen';

const Tab = createBottomTabNavigator();

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={FeedScreen} />
      <Tab.Screen name="Messages" component={MessageScreen} />
    </Tab.Navigator>
  );
}

export default HomeTabs;

シュミレーターで表示を確認すると1つの画面上にHomeとFeedのヘッダーが存在し、下部にはHomeTabsで設定したタブが表示されます。HomeTabsによってTabs Navigatorがネスト化されるためにこのような表示になっています。

1つの画面に複数のヘッダー
1つの画面に複数のヘッダー

ヘッダーが2つ表示されている問題がありますが先にNative Stack Navigatorで設定したUser画面を表示させた場合にはどのように表示されるのか確認するためにpropsのinitialRouteNameをUserに設定します。


<NavigationContainer>
  <Stack.Navigator initialRouteName="User">
    <Stack.Screen name="Home" component={Home} />
    <Stack.Screen name="User" component={UserScreen} />
    <Stack.Screen name="Setting" component={SettingScreen} />
  </Stack.Navigator>
</NavigationContainer>

User画面にタブが表示されることはありません。

ユーザ画面の確認
ユーザ画面の確認

Tabs Navigatorで設定されているFeed画面、Message画面では上部にHomeヘッダーを表示させる必要はないのでoptionsのheaderShownをfalseに設定することで非表示にすることができます。


<NavigationContainer>
  <Stack.Navigator>
    <Stack.Screen
      name="Home"
      component={Home}
      options={{ headerShown: false }}
    />
    <Stack.Screen name="User" component={UserScreen} />
    <Stack.Screen name="Setting" component={SettingScreen} />
  </Stack.Navigator>
</NavigationContainer>

Feed画面からHomeヘッダーを非表示にすることができます。Message画面でも同様にHomeヘッダーが表示されることはありません。

Feed画面からHomeヘッダーを非表示
Feed画面からHomeヘッダーを非表示

画面の移動

Tabs Navigatorで設定した画面からNative Stack Navigatorで設定した画面に移動を行いたい場合はnavigation.navigate関数を利用することができます。

FeedScreen.jsにボタンを追加しボタンをタッチするとUser画面に移動できるように設定します。


import { View, Text, Button } from 'react-native';

const FeedScreen = ({ navigation }) => {
  return (
    <View>
      <Text>フィード画面</Text>
      <Button title="ユーザ" onPress={() => navigation.navigate('User')} />
    </View>
  );
};

export default FeedScreen;

設定するとFeed画面からUser画面に移動することができます。逆にNative Stack NavigatorのUser画面からTabs NavigatorのMessage画面に移動できる確認します。

設定は先ほどFeedScreen.jsファイルで行なったことと同じでnavigation.navigateを利用します。


import { View, Text, Button } from 'react-native';

const UserScreen = ({ navigation }) => {
  return (
    <View>
      <Text>ユーザ画面</Text>
      <Button
        title="メッセージ"
        onPress={() => navigation.navigate('Message')}
      />
    </View>
  );
};

export default UserScreen;

設定するとUser画面からMessage画面に移動することができます。navigateには”Home”を指定することもできHomeを指定するとFeed画面が表示されます。Homeの後にscreenを設定することでMessage画面に移動することができます。


<Button
  title="メッセージ"
  onPress={() =>
    navigation.navigate('Home', {
      screen: 'Message',
    })
  }
/>

Native Stack + Drawer Navigator

Native Stack Navigatorの中にDrawer Navigatorをネスト化した時の動作確認を行います。

利用するコンポーネントはNative Stack + Tabs Navigatorで利用したHomeTabs.js, UserScreen.js, SettingScreen.js, FeedScreen.js, MessageScreen.jsとなります。

HomeTabs.jsのファイル名とコンポーネント名をHomeTabsからHomeDrawerに変更します。HomeDrawer.jsではcreateDrawerNavigatorを利用してDrawerの設定を行います。


import { createDrawerNavigator } from '@react-navigation/drawer';
import MessageScreen from './MessageScreen';
import FeedScreen from './FeedScreen';

const Drawer = createDrawerNavigator();

function HomeDrawer() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="Feed" component={FeedScreen} />
      <Drawer.Screen name="Message" component={MessageScreen} />
    </Drawer.Navigator>
  );
}

export default HomeDrawer;

App.jsでは変更したHomeDrawer.jsをimportします。


import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import HomeDrawer from './screens/HomeDrawer';
import UserScreen from './screens/UserScreen';
import SettingScreen from './screens/SettingScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={HomeDrawer}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="User" component={UserScreen} />
        <Stack.Screen name="Setting" component={SettingScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

シュミレータで確認するとFeedScreen画面が表示され左上にメニューのアイコンが表示されます。

左上にメニューが表示
左上にメニューが表示

メニューをタッチするとHomeDrawerで設定したFeedとMessageのリンクが表示されます。

Drawerメニューの表示
Drawerメニューの表示

FeedScreen画面からNative Stack Navigatorで設定したUser画面の移動はnavigator.navigateで行うことができます。


import { View, Text, Button } from 'react-native';

const FeedScreen = ({ navigation }) => {
  return (
    <View>
      <Text>フィード画面</Text>
      <Button title="ユーザ" onPress={() => navigation.navigate('User')} />
    </View>
  );
};

export default FeedScreen;

Drawer Navigator + Native Stack Navigator

先ほどはNative Stack Navigatorの中にDrawer Navigatorをネスト化した時の動作確認を行います。今度はその逆でDrawer Navigatorの中にNative Stack Navigatorをネスト化した時の動作を確認します。

App.jsでDrawer Navigatorの設定を行い、HomeStackコンポーネントの中でNative Stack Navigatorの設定を行います。


import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeStack from './screens/HomeStack';
import UserScreen from './screens/UserScreen';
import SettingScreen from './screens/SettingScreen';

const Drawer = createDrawerNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator>
        <Drawer.Screen name="Home" component={HomeStack} />
        <Drawer.Screen name="User" component={UserScreen} />
        <Drawer.Screen name="Setting" component={SettingScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

初期画面にHomeScreenを設定してそこからFeed画面やMessage画面に移動できるようにするためHomeScreenコンポーネントをimportします。Homeという名前はDrawer Navigatorの中で利用しているのでHomeScreenという名前にしています。


import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MessageScreen from './MessageScreen';
import FeedScreen from './FeedScreen';
import HomeScreen from './HomeScreen';

const Stack = createNativeStackNavigator();

function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="HomeScreen" component={HomeScreen} />
      <Stack.Screen name="Feed" component={FeedScreen} />
      <Stack.Screen name="Message" component={MessageScreen} />
    </Stack.Navigator>
  );
}

export default HomeStack;

HomeScreenコンポーネントからボタンでFeed画面, Message画面に移動できるようにします。


import { View, Text, Button } from 'react-native';

const HomeScreen = ({ navigation }) => {
  return (
    <View>
      <Text>ホーム画面</Text>
      <Button title="Feed" onPress={() => navigation.navigate('Feed')} />
      <Button title="Message" onPress={() => navigation.navigate('Message')} />
    </View>
  );
};

export default HomeScreen;

シュミレーターで確認するとヘッダーが2つ表示され、一つはHomeのヘッダーでメニューアイコンが表示されます。もう一つはHomeScreenです。

ヘッダーが2つ表示
ヘッダーが2つ表示

メニューをタッチするとメニュー画面が表示されます。

メニュー画面表示
メニュー画面表示

User画面に移動するとメニューは表示されたままです。

ユーザ画面
ユーザ画面

Home画面からMessage画面に移動するとHomeScreenに戻るためのリンクが表示されます。

メッセージ画面
メッセージ画面

戻る際にHomeScreenという文字をBackに変更したい場合はoptionsのheaderBackTitleVisibleをfalseに設定することで実現できます。


function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="HomeScreen" component={HomeScreen} options={{ headerBackTitleVisible: false }}/>
      <Stack.Screen name="Feed" component={FeedScreen} />
      <Stack.Screen name="Message" component={MessageScreen} />
    </Stack.Navigator>
  );
}
画面を戻る時にBack
画面を戻る時にBack

このようにDrawer NavigatorにNative Stack Navigatorをネスト化することで画面の移動を行うことができます。

Tabs Navigatorの追加

Drawer NavigatorにNative Stack Navigatorをネスト化しましたがさらにTabs NavigatgorをNative Stack Navigatorにネスト化してみましょう。

Drawer Navigatorを設定したApp.jsには変更はありません。


import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
import HomeStack from './screens/HomeStack';
import UserScreen from './screens/UserScreen';
import SettingScreen from './screens/SettingScreen';

const Drawer = createDrawerNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator>
        <Drawer.Screen name="Home" component={HomeStack} />
        <Drawer.Screen name="User" component={UserScreen} />
        <Drawer.Screen name="Setting" component={SettingScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

HomeStackコンポーネントにTabs Navigatorを設定するHomeTabsコンポーネントをimportします。先ほどまであったHomeScreenコンポーネントはHomeTabsに移動するため削除しています。


import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MessageScreen from './MessageScreen';
import FeedScreen from './FeedScreen';
import HomeTabs from './HomeTabs';

const Stack = createNativeStackNavigator();

function HomeStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="HomeTabs"
        component={HomeTabs}
      />
      <Stack.Screen name="Feed" component={FeedScreen} />
      <Stack.Screen name="Message" component={MessageScreen} />
    </Stack.Navigator>
  );
}

export default HomeStack;

HomeTabs.jsファイルではHomeScreenコンポーネントと新たにNotification.jsファイルを作成してimportしています。Notification.jsの中身はMessageScreen.jsなどを複製して作成しています。


import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import HomeScreen from './HomeScreen';
import NotificationScreen from './NotificationScreen';

const Tab = createBottomTabNavigator();

function HomeTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="HomeScreen" component={HomeScreen} />
      <Tab.Screen name="Notification" component={NotificationScreen} />
    </Tab.Navigator>
  );
}

export default HomeTabs;

設定が完了したらシュミレータで表示を確認します。ネスト化されているのでヘッダーが3つ表示されます。アイコンを変更したい場合は本文書で説明済みなので”Tabアイコンを変更”を参考に行ってください。

3つのヘッダーが表示
3つのヘッダーが表示

真ん中のHomeTabsが必要ない場合はoptionsのheaderShownをfalseにすることで非表示にすることができます。


<Stack.Navigator>
  <Stack.Screen
    name="HomeTabs"
    component={HomeTabs}
    options={{ headerShown: false }}
  />
  <Stack.Screen name="Feed" component={FeedScreen} />
  <Stack.Screen name="Message" component={MessageScreen} />
</Stack.Navigator>
HomeTabsのヘッダーを非表示
HomeTabsのヘッダーを非表示

このようなNavigatorをネスト化することで複数のNavigatorを同時に利用することができます。