React NativeでTab+Stackのナビゲーション(全画面ヘッダー付き)を実装する

## 概要

React Nativeで開発をしているときに、TabとStackを用いた画面遷移を実装しました。
また、タブを押した時に表示される画面にスタイルが共通のヘッダーが欲しかったので実装を行いました。
その方法を忘れないように自分用のメモと、自己流実装なのでもっと良いやり方があったら教えていただけたらと思っています。

動作は以下のようになります。
![hoge.gif](https://qiita-image-store.s3.amazonaws.com/0/174052/841d1f40-335e-b390-1554-2218f64fe4df.gif)


## 実行環境

* node v10.9.0
* npm v6.2.0
* watchman v4.9.0
* react-native-cli v2.0.1

また、本記事はiOS環境での動作確認を行なっています。


## プロジェクト作成

```shell
$ react-native init NavigationSample
$ npm add @babel/runtime
$ npm install
$ react-native run-ios
```

初期プロジェクトがエミュレータで起動したらOKです。


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

[React Navigation](https://reactnavigation.org/en/)というライブラリを用いて実装を行いました。

また、タブのアイコンに[react-native-vector-icons](https://github.com/oblador/react-native-vector-icons)を用いました。READMEを読んで導入作業が必要です。

```shell
$ npm install --save react-navigation
$ npm install --save react-native-vector-icons
```

## 実装

```jsx:App.js
import React, { Component } from "react";
import {
  FlatList,
  StyleSheet,
  Text,
  TouchableOpacity,
  View
} from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";
import {
  createBottomTabNavigator,
  createStackNavigator
} from "react-navigation";

// Stackのためのアイテムを用意
const stackItems = [
  {
    key: "stack1",
    name: "Stack1",
    body: "Stack1 Body"
  },
  {
    key: "stack2",
    name: "Stack2",
    body: "Stack2 Body"
  },
  {
    key: "stack3",
    name: "Stack3",
    body: "Stack3 Body"
  }
];

/* =============================================
 * 一つ目のタブに表示するコンポーネントを定義
 * StackNavigatorを入れ子にする
 * ============================================= */

// 最初にタブを選んだ時に表示されるコンポーネントを定義
const Tab1 = ({ navigation }) => (
  <View style={styles.container}>
    <FlatList
      data={stackItems}
      renderItem={({ item }) => (
        <TouchableOpacity
          key={item.key}
          style={listStyles.listItem}
          onPress={() => navigation.navigate("Detail", item)}
        >
          <Text style={{ fontSize: 24 }}>{item.name}</Text>
        </TouchableOpacity>
      )}
      contentContainerStyle={listStyles.container}
    />
  </View>
);
Tab1.navigationOptions = {
  title: "Tab1"
};

// タップした時の移動先コンポーネントを定義
const DetailScreen = ({ navigation }) => (
  <View style={detailStyles.container}>
    <Text style={detailStyles.name}>{navigation.state.params.name}</Text>
    <Text style={detailStyles.body}>{navigation.state.params.body}</Text>
  </View>
);
DetailScreen.navigationOptions = {
  title: "Stack詳細"
};

const Tab1withStack = createStackNavigator(
  {
    List: { screen: Tab1 },
    Detail: { screen: DetailScreen }
  },
  {
    initialRouteName: "List"
  }
);

/* =============================================
 * 二つ目のタブに表示するコンポーネントを定義
 * ============================================= */
const Tab2 = () => (
  <View style={styles.container}>
    <Text style={styles.welcome}>Tab2</Text>
  </View>
);
Tab2.navigationOptions = {
  title: "Tab2"
};
const Tab2withHeader = createStackNavigator(
  {
    Tab2: { screen: Tab2 }
  },
  {
    initialRouteName: "Tab2"
  }
);

/* =============================================
 * 三つ目のタブに表示するコンポーネントを定義
 * ============================================= */
const Tab3 = () => (
  <View style={styles.container}>
    <Text style={styles.welcome}>Tab3</Text>
  </View>
);
Tab3.navigationOptions = {
  title: "Tab3"
};
const Tab3withHeader = createStackNavigator(
  {
    Tab3: { screen: Tab3 }
  },
  {
    initialRouteName: "Tab3"
  }
);

/* =============================================
 * BottomTabにそれぞれのコンポーネントを紐付ける
 * ============================================= */
export default createBottomTabNavigator(
  {
    Tab1: {
      screen: Tab1withStack,
      navigationOptions: {
        title: "Tab1",
        tabBarIcon: ({ tintColor, focused }) => (
          <Icon size={20} name="star" color="#999" />
        )
      }
    },
    Tab2: {
      screen: Tab2withHeader,
      navigationOptions: {
        title: "Tab2",
        tabBarIcon: ({ tintColor, focused }) => (
          <Icon size={20} name="star" color="#999" />
        )
      }
    },
    Tab3: {
      screen: Tab3withHeader,
      navigationOptions: {
        title: "Tab3",
        tabBarIcon: ({ tintColor, focused }) => (
          <Icon size={20} name="star" color="#999" />
        )
      }
    }
  },
  {
    initialRouteName: "Tab1"
  }
);

/* =============================================
 * 以下、スタイルの定義
 * ============================================= */
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF"
  },
  welcome: {
    fontSize: 20,
    textAlign: "center",
    margin: 10
  }
});

const listStyles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF",
    paddingHorizontal: 16
  },
  listItem: {
    marginVertical: 12
  }
});

const detailStyles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#F5FCFF"
  },
  name: {
    fontSize: 23,
    fontWeight: "bold",
    color: "#000"
  },
  body: {
    fontSize: 18,
    color: "#737373"
  }
});
```

上記コードで意図したような画面遷移を実現することができました。