【RN】StackとBottomTabNavigatorを組み合わせたサンプル
はじめに
今回はReact Native
でアプリを作る上基本となるStackNavigator
とBottomTabNavigator
を組み合わせた画面構成のサンプルについて紹介していきます。
なお、コードはTypescript
で記載します。
用語解説
先に、今回の記事で紹介する各用語について紹介していきます。
StackNavigator
StackNavigator
は主にはアプリにおける「一覧画面」と「詳細画面」のような関係性がある画面間の遷移の用いられます。
特徴として画面が手前に積み重なっていくような挙動をし、iOS
の右スワイプ(画面の左端を掴んで右にスワイプ)やAndroid
の戻るボタンで1つ前の画面に戻ることができます。
BottomTabNavigator
アプリにおいて機能がいくつかのカテゴリに分かれている場合にBottomTabNavigator
は用いられます。
Twitter
やFacebook
やInstagram
など、多機能なアプリは大概このBottomTabNavigator
で画面制御を行っています。
インストール
今回はreact-navigation
を使用していきます。
react-navigation
はメジャーバージョンごとに実装方法が大きく異なってきますが、ここでは記事執筆時点(2021/07/14)時点で最新バージョンであるv5
系を基準としていきます。
ReactNative
のプロジェクトは作成済みのものとして、以下のライブラリを追加していきましょう。
yarn add @react-navigation/native react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view @react-navigation/stack @react-navigation/bottom-tabs
package.json
のdependencies
は以下のようになりました。
"@react-native-community/masked-view": "^0.1.11",
"@react-navigation/bottom-tabs": "^5.11.11",
"@react-navigation/native": "^5.9.4",
"@react-navigation/stack": "^5.14.5",
"react": "17.0.1",
"react-native": "0.64.1",
"react-native-gesture-handler": "^1.10.3",
"react-native-reanimated": "^2.2.0",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.4.0",
iOS
の場合はpod install
を行います。
cd ios && pod install
実装
それでは早速実装例に移りたいと思います。
完成イメージは以下の通りとなっています。
BottomTabNavigator
として2画面、そしてそれぞれの画面から派生する画面として2画面をStackNavigator
に用意します。
ヘッダーの実装
先に、各画面で表示させるヘッダーを作成します。
実は後ほど実装するStackNavigator
の画面にはプロパティとしてヘッダーを指定することができます。
ただ、今回のようにBottomTabNavigator
を組み合わせて扱う場合、BottomTabNavigator
の中の画面によってヘッダの表示を分けたいケースが多いです。
そういった場合に、StackNavigator
のヘッダーだけではカスタムしにくい箇所が発生するため、今回はヘッダーを自作して使用しています。
構造はいたってシンプルで、「表示するタイトル」とヘッダーの左側、右側に表示する要素を親から指定できるようにしています。
Header.tsx
import React from 'react';
import {View, Text} from 'react-native';
type Props = {
/** ヘッダに表示するタイトル */
title: string;
/** ヘッダ左側の要素 */
left?: React.ReactNode;
/** ヘッダ右側の要素 */
right?: React.ReactNode;
};
/**
* オリジナルのヘッダコンポーネント
* @param props
* @returns
*/
const Header: React.VFC<Props> = props => {
const {title, left, right} = props;
return (
<View
style={{
height: 60,
flexDirection: 'row',
alignItems: 'center',
}}>
<View style={{flex: 0.25}}>{left}</View>
<Text
style={{
flex: 1,
textAlign: 'center',
alignItems: 'center',
}}>
{title ?? ''}
</Text>
<View style={{flex: 0.25}}>{right}</View>
</View>
);
};
export default Header;
TabNavigatorの実装
つづいてTabNavigator
関連の画面を作ります。
といっても今回は先ほど作成したHeader
を使っていること以外は<Text>
を表示するだけのシンプルな画面を2種類用意します。
Tab1.tsx
import React, {ReactNode} from 'react';
import {useMemo} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import Header from '../Common/Header';
/** Tab1 */
const Tab1: React.VFC = () => {
const navigation = useNavigation();
const right: ReactNode = useMemo(() => {
return (
<TouchableOpacity
style={{padding: 10}}
onPress={() => {
navigation.navigate('Stack1');
}}>
<Text>Hoge</Text>
</TouchableOpacity>
);
}, [navigation]);
return (
<View style={{flex: 1}}>
<Header title="Tab1" right={right} />
<View
style={{
flex: 1,
justifyContent: 'center',
alignContent: 'center',
backgroundColor: '#c9ff93',
}}>
<Text style={{textAlign: 'center'}}>This Screen is Tab1.</Text>
</View>
</View>
);
};
export default Tab1;
Tab2.tsx
import React, {ReactNode} from 'react';
import {useMemo} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import Header from '../Common/Header';
/** Tab1 */
const Tab2: React.VFC = () => {
const navigation = useNavigation();
const right: ReactNode = useMemo(() => {
return (
<TouchableOpacity
style={{padding: 10}}
onPress={() => {
navigation.navigate('Stack2');
}}>
<Text>Fuga</Text>
</TouchableOpacity>
);
}, [navigation]);
return (
<View style={{flex: 1}}>
<Header title="Tab2" right={right} />
<View
style={{
flex: 1,
justifyContent: 'center',
alignContent: 'center',
backgroundColor: '#8ec6ff',
}}>
<Text style={{textAlign: 'center'}}>This Screen is Tab2.</Text>
</View>
</View>
);
};
export default Tab2;
上記の2種類の画面を作成したらいよいよBottomTabNavigator
の作成です。
作成自体はcreateBottomTabNavigator()
で行います。
作成したTab
インスタンスにScreen
として2種類の画面を追加します。
import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import Tab1 from './Tab1';
import Tab2 from './Tab2';
const Tab = createBottomTabNavigator();
/** TabNavigator */
const TabNavigator: React.VFC = () => {
return (
<Tab.Navigator>
<Tab.Screen name="Tab1" component={Tab1} />
<Tab.Screen name="Tab2" component={Tab2} />
</Tab.Navigator>
);
};
export default TabNavigator;
StackNavigatorの実装
次にStackNavigator
の実装です。
まずはBottomTabNavigator
の時と同様に、配置する2画面を作成します。
Stack1.tsx
import React, {ReactNode} from 'react';
import {useMemo} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import Header from '../Common/Header';
/** Stack1 */
const Stack1: React.VFC = () => {
const navigation = useNavigation();
const left: ReactNode = useMemo(() => {
return (
<TouchableOpacity
style={{padding: 10}}
onPress={() => {
navigation.goBack();
}}>
<Text>戻る</Text>
</TouchableOpacity>
);
}, [navigation]);
return (
<View style={{flex: 1}}>
<Header title="Stack1" left={left} />
<View
style={{
flex: 1,
justifyContent: 'center',
alignContent: 'center',
backgroundColor: '#ff93c9',
}}>
<Text style={{textAlign: 'center'}}>This Screen is Stack1.</Text>
</View>
</View>
);
};
export default Stack1;
Stack2.tsx
import React, {ReactNode} from 'react';
import {useMemo} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import Header from '../Common/Header';
/** Stack2 */
const Stack2: React.VFC = () => {
const navigation = useNavigation();
const left: ReactNode = useMemo(() => {
return (
<TouchableOpacity
style={{padding: 10}}
onPress={() => {
navigation.goBack();
}}>
<Text>戻る</Text>
</TouchableOpacity>
);
}, [navigation]);
return (
<View style={{flex: 1}}>
<Header title="Stack2" left={left} />
<View
style={{
flex: 1,
justifyContent: 'center',
alignContent: 'center',
backgroundColor: '#cc99ff',
}}>
<Text style={{textAlign: 'center'}}>This Screen is Stack2.</Text>
</View>
</View>
);
};
export default Stack2;
画面の作成が終わったらStackNavigator
の作成です。
作成した2画面に加えて、先ほどのBottomTabNavigator
をStack.Screen
として加えています。
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import TabNavigator from '../BottomTab/TabNavigator';
import Stack1 from './Stack1';
import Stack2 from './Stack2';
const Stack = createStackNavigator();
/** TabNavigator */
const StackNavigator: React.VFC = () => {
// screenOptionsでheaderShown:falseを指定して、デフォルトではヘッダを表示させない
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="Tab" component={TabNavigator} />
<Stack.Screen name="Stack1" component={Stack1} />
<Stack.Screen name="Stack2" component={Stack2} />
</Stack.Navigator>
);
};
export default StackNavigator;
アプリ全体に反映
最後に、今まで作成してきたものをアプリに反映させます。
StackNavigator.tsx
をApp.tsx
内に配置しましょう。
react-navigation
のNavigator
を使う注意点として、必ずNavigationContainer
の直下に1つだけNavigator
を配置するようにします。
import React from 'react';
import {SafeAreaView} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import StackNavigator from './src/Stack/StackNavigator';
const App: React.VFC = () => {
return (
<SafeAreaView style={{flex: 1}}>
<NavigationContainer>
<StackNavigator />
</NavigationContainer>
</SafeAreaView>
);
};
export default App;
これで冒頭のサンプルのように動作するようになりました。
まとめ
今回はStackNavigator
とBottomTabNavigator
を使ったアプリの画面サンプルについてご紹介しました。
扱ったコード全体は私のリポジトリ内にも載せておきました。
ここにRedux
やらFirebase
やAWS
といったバックエンドとの接続を加えて、実際のアプリ開発を進めていくイメージになります。
今回は画面の遷移をuseNavigation()
を使って行っていますが、Redux
を使う場合は画面遷移のアクションをdispatch
していく形にする手もあります。
今回の内容が参考になれば幸いです。
Discussion
フッタータブの実装で非常に助かりました!ありがとうございました!