React-Navigatorを利用してみる（基礎編）


## 注意

* react-navigation v3で動作確認しています
* react-navigation v4がリリースされ、createBottomTabNavigator等が分離されています（未対応）。

## 概要

まともなアプリを作るためにはNavigation機能の利用は避けて通れません。
ライブラリとしては[react-navigation](https://reactnavigation.org/)と[react-native-router-flux(rnrf)](https://github.com/RNRF/react-native-router-flux)というのが有名みたいですが、Reac自体に不慣れなので情報が多いreact-navigationを利用してみます。

この基礎編をベースにした応用編んも書きました。よろしければどうぞ。

* [React-Navigatorを利用してみる（基礎編）](https://qiita.com/zaburo/private/5b69a65db1b4d181896a)→この記事
* [RN応用編１：Drawer Navigatorをハンバーガーメニューで表示させる](https://qiita.com/zaburo/private/aaf198c79520dbaa48e2)
* [RN応用編２：TabやDrawerメニューにアイコンを設定する](https://qiita.com/zaburo/private/05c41190726cbcb17fdf)
* [RN応用編３：Reduxで値の取り回し](https://qiita.com/zaburo/private/c7df04fa1d53fd08559c)


## React Navigation(react-navigation：以下RN）

RNでは3つのナビゲーション機能を提供しています。

* Stack Navigation
* Tab Nabigation
* Drawer Nabigation

このそれぞれの実装方法を見てみます。

## 準備

### 作業ディレクトリの作成と必要モジュールのインストール

必要なものをインストールします。
私はexpoを利用していますが、create-react-native-appとかでも動くと思います。

>expoがインストールされていない場合は、npm install -g expoで。

```bash
expo init navigation
cd navigation
npm install --save react-navigation
npm install
```

>expo24ではreact-native-gesture-handlerに関するWarningがでます。[ここ](https://qiita.com/zaburo/items/81c39f73216e87f443ea)を見て解決を。

### デモに必要なファイルを生成

移動先として利用するページ（Screen）を先に作っておきます。

```bash
mkdir screens
cd screens
touch Tab1.js Tab2.js Stack1.js Stack2.js Single1.js Single2.js
```

### 各ファイルと役割

作成したファイルは各Navigationで以下のように利用していく予定です。

<img width="1079" alt="スクリーンショット 2018-10-28 7.53.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/55188/a8b79ca4-9e36-20b0-42c1-93c4539248e1.png">

### 各ファイル記述

とりあえず画面遷移だけ確認できればいいので、各ページ名を表示するだけの簡単な記述を行っていきます。
ページ名だけが違うほぼ同じ内容ですが、作業の基本ファイルとなるので、冗長ですが各ファイルの内容を記述しておきます。


#### screens/Single1.js

```js:Single1
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Single1 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Single1</Text>
            </View>
        );
    }
}
```

#### 注意：export defaultする位置

このテンプレートでは、classの宣言と一緒にexportしていますが、後々（Reduxの利用）とかを想定すると、classの宣言とexportは分けてもいいかもしれません。

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

class Single1 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Single1</Text>
            </View>
        );
    }
}

+export default Single1;
```

#### screens/Single2.js

```js:Single2
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Single2 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Single2</Text>
            </View>
        );
    }
}
```

#### screens/Stack1.js

```js:Stack1.js
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Stack1 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Stack1</Text>
            </View>
        );
    }
}
```

#### screens/Stack2.js

```js:Stack2.js
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Stack2 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Stack2</Text>
            </View>
        );
    }
}
```

#### screens/Tab1.js

```js:Tab1.js
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Tab1 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Tab1</Text>
            </View>
        );
    }
}
```

#### screens/Tab2.js

```js:Tab2.js
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Tab2 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Tab2</Text>
            </View>
        );
    }
}
```

#### App.js

記述が完了したらアプリ実行の起点となるApp.jsファイルでscreens/*以下のファイルをまとめてimportしておきます。
で、とりあえず表示を確認したいので、\<Single1 />を呼び出す記述を行い動作確認をしてみます。

>最近のexpoが出力するテンプレートはファンクションコンポーネントベースになっていますが（おそらくHookが追加されたため）、ここではクラスコンポーネントに書き換えています。

```js:App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

//import screens
import Single1 from './screens/Single1';
import Single2 from './screens/Single2';
import Stack1 from './screens/Stack1';
import Stack2 from './screens/Stack2';
import Tab1 from './screens/Tab1';
import Tab2 from './screens/Tab2';

export default class App extends React.Component {
    render() {
        return (
            <Single1/>
        );
    }
}
```

### 動作確認

コンソールで以下のコマンドを打ちます。

```bash
expo start
```

Webが立ち上がるので検証したい環境で検証します。私はiOS Simulatorを選んでます。

<img width="545" alt="スクリーンショット 2018-10-27 19.09.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/55188/7711fb45-f3a3-8148-cdb2-91d07e3c3372.png">

動いているようです。
ここまでに用意したテンプレートを利用して各ナビゲーション機能を試してみます。

## Stack Navigatorの実装

最も基本的なStack Navigatorを試します。次へ次へと移動するようなやつです。
動作を言葉で表現しづらいですが、まあ、動かせばわかります。

以下、diffモードで差分だけ見ていきます。

>動作させる際にはコード中の+は削除してくださいね。念の為。

#### App.js

ではStack Navigationの実装を行います。
やることは、StackNavigator（ここではStack）の定義をcreateStackNavigator()で行い、それを呼び出すだけですが、createStackNavigatorを利用するためのimport等も記述します。

>後で利用するcreateBottomTabNavigatorとcreateDrawerNavigatorもimportしておきます。

なお、React-Navigation V3からAppContainerでラップする必要があります。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
+import { createStackNavigator, createBottomTabNavigator, createDrawerNavigator, createAppContainer } from 'react-navigation';

//import screens
import Single1 from './screens/Single1';
import Single2 from './screens/Single2';
import Stack1 from './screens/Stack1';
import Stack2 from './screens/Stack2';
import Tab1 from './screens/Tab1';
import Tab2 from './screens/Tab2';

//stack
+const Stack = createStackNavigator(
+    {
+        Stack1: {screen:Stack1},
+        Stack2: {screen: Stack2},
+    },
+    {
+        initialRouteName: 'Stack1'
+    }
+);

export default class App extends React.Component {
    render() {

        //AppContainerでラップ
+        const Layout = createAppContainer(Stack);

        return (
            <Layout/>
        );
    }
}
```

#### screens/Stack1.js

* 初期画面となるStack1にStack2への移動ボタンをつけます（Stack1へ戻るボタンは自動生成されます）。

```diff:Stack1.js
import React from 'react';
import { View, Text, Button } from 'react-native';

export default class Stack1 extends React.Component {
    render() {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Stack1</Text>
                <Button
                    title='GoTo Stack2'
+                   onPress={() => this.props.navigation.navigate('Stack2')}
                />
            </View>
        );
    }
}
```

では動作確認してみます。

<img width="545" alt="スクリーンショット 2018-10-27 19.19.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/55188/0898502f-7e23-0a5f-7b0c-18dced33af58.png">

できました。簡単です。

## Tab Navigatorの実装

次にTab Navigatorを実装します。
上記で作成したStackのコードを残したままなので長く見えますが、数行追加するだけです。

>DrawerでStackを利用するので記述を残しています。

screensの方はいじりません。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { createStackNavigator, createBottomTabNavigator, createDrawerNavigator, createAppContainer } from 'react-navigation';

//import screens
import Single1 from './screens/Single1';
import Single2 from './screens/Single2';
import Stack1 from './screens/Stack1';
import Stack2 from './screens/Stack2';
import Tab1 from './screens/Tab1';
import Tab2 from './screens/Tab2';

//stack
const Stack = createStackNavigator(
    {
        Stack1: { screen: Stack1 },
        Stack2: { screen: Stack2 },
    },
    {
        initialRouteName: 'Stack1'
    }
);

+//Tab
+const Tab = createBottomTabNavigator(
+    {
+        Tab1: { screen: Tab1 },
+        Tab2: { screen: Tab2 },
+    }
+);

export default class App extends React.Component {
    render() {

        //AppContainerでラップ
+       const Layout = createAppContainer(Tab);

        return (
           <Layout />
        );
    }
}
```

<img width="545" alt="スクリーンショット 2018-10-27 19.22.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/55188/d17ee19a-07c0-7194-49f1-b6d5c74d8136.png">

出来ました。こちらも簡単でした。

### 参考：Tabの要素にStackを入れ子にする

下記のようにするとStackをTabのコンテンツの１つとして扱うこともできます。

>ここではソースは変更いませんが、試したい人はどうぞ。

```diff:App.js(抜粋）
.....
//stack
const Stack = createStackNavigator(
  {
    Stack1: { screen: Stack1 },
    Stack2: { screen: Stack2 },
  },
  {
    initialRouteName: 'Stack1'
  }
);

//tab
const Tab = createBottomTabNavigator(
  {
+    Tab1: { screen: Stack },
    Tab2: { screen: Tab2 }
  }
);
.....
```

### 参考：Tabの要素をStackNavigatorでラップする

StackNavigatorを利用するとHeader部分に（いい意味でも悪い意味でも）一定のスペースが確保されますが、素のページにはスペースが無いので、見た目がちぐはぐになるため、素ページをStackNavigatorでラップして見た目をととのえます。

例えば下記のようにします。

```
//Tab
const Tab = createBottomTabNavigator(
    {
        Tab1: { screen: Stack },
        Tab2: { screen: createStackNavigator({ Tab2: { screen: Tab2 } }) },
    }
);
```

## Drawer Navigator

最後にDrawer Navigationを実装します。画面左端を右にスワイプ？すれば、出てくる画面です。
口で説明するのが難しいですね。。。まあ、動かせばわかります。横向きのタブ？的なものです。

>いくつかのサンプルではヘッダ領域にハンバーガーアイコンを表示させて、それをクリックすることで表示させているものもありますが、基本、アイコン等が無くてもスワイプで開きます（ヘッダのボタンで表示させるのは、応用で書こうかと思います）。

ここでは、今までに開発したStack, Tab, Single1, Single2を全て表示させてみます。

```diff:App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { createStackNavigator, createBottomTabNavigator, createDrawerNavigator, createAppContainer } from 'react-navigation';

//import screens
import Single1 from './screens/Single1';
import Single2 from './screens/Single2';
import Stack1 from './screens/Stack1';
import Stack2 from './screens/Stack2';
import Tab1 from './screens/Tab1';
import Tab2 from './screens/Tab2';

//stack
const Stack = createStackNavigator(
    {
        Stack1: { screen: Stack1 },
        Stack2: { screen: Stack2 },
    },
    {
        initialRouteName: 'Stack1'
    }
);

//Tab
const Tab = createBottomTabNavigator(
    {
        Tab1: { screen: Tab1 },
        Tab2: { screen: createStackNavigator({ Tab2: { screen: Tab2 } }) },
    }
);

+//drawer
+const Drawer = createDrawerNavigator(
+    {
+        Stacks: { screen: Stack },
+        Tabs: { screen: Tab },
+        Single1: { screen: Single1 },
+        Single2: { screen: Single2 },
+    },
+    {
+        initialRouteName: 'Tabs'
+    }
+);

export default class App extends React.Component {
    render() {

        //AppContainerでラップ
+       const Layout = createAppContainer(Drawer);

        return (
            <Layout />
        );
    }
}
```

動作確認してみます。

>普通に起動したときにはただStack1が表示されるだけです。画面左端をスワイプしてください。

<img width="545" alt="スクリーンショット 2018-10-27 19.28.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/55188/c3864649-1e42-e0d9-e595-d61f4e306f6e.png">

以上です。動きました。
わかればすごく簡単なのですが、機能の依存関係などを把握するまでに時間がかかりますね。


## 応用編

基本編をベースにした応用編も書きました。合わせてご覧ください。

* [RN応用編１：Drawer Navigatorをハンバーガーメニューで表示させる](https://qiita.com/zaburo/private/aaf198c79520dbaa48e2)
* [RN応用編２：TabやDrawerメニューにアイコンを設定する](https://qiita.com/zaburo/private/05c41190726cbcb17fdf)
* [RN応用編３：Reduxで値の取り回し](https://qiita.com/zaburo/private/c7df04fa1d53fd08559c)