18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React Native で画面遷移やタブメニュー【react-navigation 5.x】

Last updated at Posted at 2020-03-05

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;

おまけ

スクリーンのなかで画面遷移をする場合、propsuseNavigation から 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 を使ってみても良いかもしれません。

型定義周りはもう少し調査が必要そうです。

記事の内容に誤りなどございましたらご指摘いただけると幸いです。

18
19
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?