react-navigation とは
react-navigation とは、React Native アプリのルーティングで使える便利なライブラリです。
画面遷移や、タブバー、そしてドローワー(横からすっとでてくるメニュー)などを利用できるので非常に便利。
4.x->5.x のメジャーバージョンアップに伴い、コンポーネントベースになりました。
今回は、5.x の使い方を紹介します。
インストール
# ライブラリのインストール
yarn add @react-navigation/native
# dependencies のインストール
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view
App.tsx
import { NavigationContainer } from '@react-navigation/native';
import React, { useEffect } from 'react';
import HomeNavigator from './Home';
import NavigationService from './navigation-service';
const App: React.FunctionComponent = () => {
return (
<NavigationContainer>
<HomeNavigator />
</NavigationContainer>
);
};
export default App;
これで準備は完了です。
画面遷移の実装
ライブラリをインストールします。
yarn add @react-navigation/stack
サンプルコードは以下のようになります。
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { Alert, Button } from 'react-native';
const Stack = createStackNavigator();
const HomeNavigator = () => (
<Stack.Navigator
screenOptions={{
headerTintColor: ...,
headerStyle: {
backgroundColor: ...,
},
}}
>
<Stack.Screen
name="Home"
component={Home}
options={{
headerRight: () => (
<Button
onPress={() => Alert.alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>
<Stack.Screen name="Details" component={Details} />
</Stack.Navigator>
);
...
export default HomeNavigator;
最初に createStackNavigator
でスタックを定義しています。
作成したスタックは Stack.Navigator
, Stack.Screen
の形で使用します。
screenOptions
には、このナビゲーター全体に適用するレイアウトを定義することができます。
個々のスクリーンに適用するレイアウトは options
に記載します。
component
で画面の要素を見ていきます。
二種類の書き方ができます。
const Home: React.FunctionComponent = () => {
const navigation = useNavigation();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
};
const Home: React.FunctionComponent = ({ navigation }) => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
};
navigation
を渡す方法は、 react-navigation 4.x
までと変わりませんね。
props
として渡す方法の型定義が面倒になってしまったため、私は Hooks の方を使ってます。
ボタンをクリックすると、 Details に遷移します。
タブメニューの実装
ライブラリをインストールします。
yarn add @react-navigation/bottom-tabs
サンプルコードは以下のようになります。
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import React, { useLayoutEffect } from 'react';
import Ionicons from 'react-native-vector-icons/Ionicons';
const Tab = createBottomTabNavigator();
const TabNavigator = ({ navigation, route }) => {
useLayoutEffect(() => {
navigation.setOptions({
headerTitle: route.state
? route.state.routes[route.state.index].name
: route.params?.screen || 'Home',
});
}, [navigation, route]);
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused
? 'ios-information-circle'
: 'ios-information-circle-outline';
} else if (route.name === 'Settings') {
iconName = focused ? 'ios-list-box' : 'ios-list';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
})}
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
);
};
...
export default TabNavigator;
createBottomTabNavigator
でタブを作成。
Tab.Navigator
, Tab.Screen
で実際の内容を定義します。
screenOptions
, tabBarOptions
の代わりに、カスタムのタブメニューを利用することも可能です。 tabBar={(props) => <TabBar {...props} />}
で定義します。
TabBar
は以下のような形で書くことができます。
import React from 'react';
import { Dimensions, Text, View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
const { width: windowWidth } = Dimensions.get('window');
const TabBar = ({ state, descriptors, navigation }) => {
const tabWidth = windowWidth / state.routes.length;
return (
<View style={{ flexDirection: 'row', width: windowWidth, height: 64 }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
const onLongPress = () => {
navigation.emit({
type: 'tabLongPress',
target: route.key,
});
};
return (
<TouchableOpacity
key={route.key}
accessibilityRole="button"
accessibilityStates={isFocused ? ['selected'] : []}
accessibilityLabel={options.tabBarAccessibilityLabel}
onPress={onPress}
onLongPress={onLongPress}
style={{
flex: 1,
width: tabWidth,
paddingTop: 10,
}}
>
<Text
style={{
color: isFocused ? '#673ab7' : '#222',
alignSelf: 'center',
}}
>
{label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
};
export default TabBar;
スライドメニューの実装
ライブラリをインストールします。
yarn add @react-navigation/drawer
サンプルコードは以下のようになります。
import { createDrawerNavigator } from '@react-navigation/drawer';
import React from 'react';
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Notifications" component={Notifications} />
</Drawer.Navigator>
);
};
...
export default DrawerNavigator;
おまけ
スクリーンのなかで画面遷移をする場合、props
か useNavigation
から navigation
をうけとって使用する方法を紹介しました。
では、自前の関数など、スクリーン以外で利用する場合はどうすれば良いでしょうか。
navigation-service.ts
import React from 'react';
export const isMountedRef = React.createRef();
export const navigationRef = React.createRef();
const navigate = (name: string, params?: any) => {
if (isMountedRef.current && navigationRef.current) {
navigationRef.current?.navigate(name, params);
} else {
console.log('Not mounted.');
}
};
export default {
navigate,
};
App.tsx
const App: React.FunctionComponent = () => {
useEffect(() => {
// TypeScript error が出る
isMountedRef.current = true;
return () => (isMountedRef.current = false);
}, []);
return (
<NavigationContainer ref={navigationRef as any}>
<HomeNavigator />
</NavigationContainer>
);
};
公式ドキュメントでも紹介されている方法ですが、 isMountedRef.current の更新はうまくできないようです。
GitHub にも issue がありました。
コンポーネントがマウントされていれば navigationRef.current?.navigate(...);
は動作します。
これで自前の関数から、
const myFunc = () => {
NavigationService.navigate('...');
}
といった形で利用可能になります。
おわりに
別プロジェクトでは 4.x からバージョンアップを試みており、 TypeScript 周りでかなり苦労しています。
ただし、 Hooks との相性も良さそうですし、新規でプロジェクトを始める場合は 5.x を使ってみても良いかもしれません。
型定義周りはもう少し調査が必要そうです。
記事の内容に誤りなどございましたらご指摘いただけると幸いです。