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

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

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

環境を構築する

本文書ではmacOSの環境にNode.js, Expoがインストール済みでexpo initコマンドでプロジェクトを作成するところから開始しています。

プロジェクト名には任意のreact-native-navigationという名前をつけています。実行後はtemplateを選択することができるので一番上のblankを選択します。


% expo init react-native-navigation

インストール完了後、プロジェクトフォルダに移動してnpm startコマンドを実行するとDeveloper toolsの画面が表示されます。


% cd react-native-navigation
% npm start
Developer tools
Developer tools

iOSのシュミレーターを起動するために左のメニューからRun on iOS simulatorをクリックするとしばらく起動に時間がかかりますがiOSのシュミレーターが起動します。このシュミレーターを利用しながら動作確認を行なっていきます。

iOSのシュミレーター起動
iOSのシュミレーター起動
npm startコマンドを実行したコンソールにはエラーメッセージが表示させるだけではなくコマンドを実行することができます。コマンドはShitf + ?で表示されます。画面のリロードを行いたい時はコンソールで”r”を打つとリロードが行われます。シュミレーター上でリロードを行いたい場合は”ctrl + d” + “Cmd + d”なのでコンソールの方がリロードは簡単です。

React Navigatorの設定

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

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

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


% npm install @react-navigation/native

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


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

React Navigatorではナビゲーション(画面の移動)する方法として大きく3つ(Stack, Tabs, Drawer)あります。その3つもさらに細かく分けれており6つのライブラリ(Stack, Native Stack, Drawer, Bottom Tabs, Material Bottom Tabs, Material Top Tabs)に分かれています。どのナビゲーターを利用するかによってインストールするライブラリが異なります。アプリ内で複数のナビゲーションを利用することも可能です。

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

Native Stack Navigatorの設定

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


% npm install @react-navigation/native-stack

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

プロジェクトフォルダにscreensを作成してHomeScreen.jsを作成します。React Navigationによる画面の移動に注文しているので本文書ではstyleの設定は行いません。


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

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

export default HomeScreen;

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


import React from 'react';
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としています。ここまでの設定でシュミレーターを確認すると下記のように表示されます。上部のHomeはStack.Screenタグのname propsで指定したHomeです。画面の上部にヘッダーが自動で付与され画面の名前が表示されます。

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

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


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

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

export default UserScreen;

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


import React from 'react';
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コンポーネントを追加しても何も変化はありません。initialRouteName prpsをStack.Navigatorコンポーネントで設定することで最初に表示する画面を指定することができます。initialRouteNameをUserに変更することでUser画面が表示させることを確認してください。デフォルトでは一番上の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 React from 'react';
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 React from 'react';
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 React from 'react';
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関数によって画面の移動を行うことができます。

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 React, { 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 React, { 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 React from 'react';
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 React from 'react';
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”が表示されます。

Tab Navigationの設定

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

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


 % npm install @react-navigation/bottom-tabs

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


import React from 'react';
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 React from 'react';
import { View, Text } from 'react-native';

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

export default HomeScreen;

import React from 'react';
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="ios-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="ios-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 React from 'react';
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

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


import React from 'react';
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

さらにreact-native-gesture-handlerもインストールする必要があります。Expoを利用している場合にはexpoコマンドを利用してインストールを行います。


 % expo install react-native-gesture-handler

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


import React from 'react';
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と同様に画面間を移動することができます。

ここまでの動作確認で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を追加します。

内容はUserScreen.jsを複製し、コンポーネント名とTextタグの中に入れる文字列のみ変更を行います。MessageScreen.jsの場合は下記のようになります。FeedScreen.js, SettingScree.jsファイルも同じように作成してください。


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

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

export default MessageScreen;

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


import React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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 React from 'react';
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を同時に利用することができます。