React Navigation

2021/04/22

インストール

yarn でパッケージをインストールする;

yarn add @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs

Expo に関連パッケージを認識させるために、expo install を実行;

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

Stack ナビゲーションの作成

export type RootStackParamList = {
  Main: undefined;
  SignUp: undefined;
};

routes.ts を作成し、ルーティング情報を型として定義します。undefined になっているところは、ルーティングする際に id や order などの情報を追加付与する際に、その型情報を記載する箇所です。(引数がなければ undefined で構いません)

import { RootStackParamList } from './routes';

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

import { SignUp } from './SignUp';
import { Main } from './Main';

const Stack = createStackNavigator<RootStackParamList>();

export const Navigation = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Main">
        <Stack.Screen name="Main" component={Main} options={{ title: 'メイン画面' }} />
        <Stack.Screen name="SignUp" component={SignUp} options={{ title: 'メンバー登録' }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};
  • name がルーティング名になる。大文字小文字を無視する。大文字含む名称が推奨されている
  • createStackNaviagtor に型を付ける。各コンポーネントの引数をまとめた型を作ると、name に型制約がつく
  • options で表示名などを指定できる

各 Screen の作成

import React from 'react';
import { StackNavigatStackScreenPropsionProp } from '@react-navigation/stack';

import { RootStackParamList } from '../Navigation';

export type MainProps = StackScreenProps<RootStackParamList, 'Main'>;

export const Main = ({ navigation }: MainProps) => (
  <Box w="100%" maxW="100%" h="100%" display="flex" justifyContent="center" alignItems="center">
    <VStack spacing={4} h="100%" px={4} py={8} bgColor={palette.white}>
      <Typography color={palette.brown} fontSize="xx-large">
        メイン画面
      </Typography>

      <Center>
        <Button label="メンバー登録" onPress={() => navigation.navigate('SignUp')} />
      </Center>
    </VStack>
  </Box>
);
  • RootStackParamList をもとに MainNavigationProps を作る
  • Main コンポーネントに react-navigation から navigation が渡されるので、それをもとにスタック制御をする
  • navigation.navigate(name) で対象の画面へ遷移できる

引数の授受(/users/:id のようなもの)

export type RootStackParamList = {
  Main: undefined;
  SignUp: undefined;
  UserDetail: { id: string }; // 受け渡ししたい引数を定義する
};

引数として渡したいパラメータの型定義を付与する。

<Button label="ユーザ画面" onPress={() => navigation.navigate('UserDetail', { id: '42' })} />

任意の画面で navigate を呼び出すと、引数に型がついており、正しい引数だけを渡せるようになっている。

export type UserDetailProps = StackScreenProps<RootStackParamList, 'UserDetail'>;

export const UserDetail = ({ navigation, route }: UserDetailProps) => (
  <Box w="100%" maxW="100%" h="100%" display="flex" justifyContent="center" alignItems="center">
    <VStack spacing={4} h="100%" px={4} py={8} bgColor={palette.white}>
      <VStack spacing={4}>
        <Typography color={palette.brown} fontSize="xx-large">
          {`ユーザ画面 id=${route.params.id}`}
        </Typography>
      </VStack>

      <Center>
        <Button label="メイン画面へ戻る" onPress={() => navigation.navigate('Main')} />
      </Center>
    </VStack>
  </Box>
);
  • route.params に型がついた props が渡されるので、これを利用すれば OK。
  • setParams でパラメータの更新が可能
  • initialParams を指定しておくことが可能
  • 親画面の params を書き換えることも可能(e.g.ユーザ作成してユーザが増えた)。この場合は navigate か goBack に引数を指定すれば良い。{merge: true}すると合成してくれる模様。

ヘッダのスタイリング

export const Navigation = () => {
  const theme = useContext<ThemeColors>(ThemeContext);
  const screenOptions: StackNavigationOptions = {
    headerStyle: { backgroundColor: theme.secondary },
    headerTintColor: palette.white,
    headerTitleStyle: { fontWeight: 'bold' },
  };

  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Main" screenOptions={screenOptions}>
        <Stack.Screen name="Main" component={Main} options={{ title: 'メイン画面' }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};
  • Navigator に screenOptions を指定すると、ヘッダスタイルを変更できる
  • スタイルではなくコンポーネントを指定することも可能
  • 個別の Screen に対して screenOptions を指定することもできる

タブナビゲーションの追加

import { RouteProp } from '@react-navigation/core';
import {
  BottomTabBarOptions,
  BottomTabNavigationOptions,
  createBottomTabNavigator,
} from '@react-navigation/bottom-tabs';
import Feather from 'react-native-vector-icons/Feather';

// main tab に属する screen 一覧
export type MainTabParamList = {
  Home: undefined;
  UserDetail: { id: string };
};

// route.name と iconName をマッチさせる辞書
const tabIconNames: Record<keyof MainTabParamList, string> = {
  Home: 'home',
  UserDetail: 'user',
};

// route.name と color, size を使って Feather アイコンをレンダリング
const getScreenOptions = (route: RouteProp<MainTabParamList, keyof MainTabParamList>) =>
  ({
    tabBarIcon: ({ color, size }) => (
      <Feather name={tabIconNames[route.name]} size={size} color={color} />
    ),
  } as BottomTabNavigationOptions);

// 下タブのナビゲーションを作成
const Tab = createBottomTabNavigator<MainTabParamList>();

export const Main = () => {
  const theme = useContext<ThemeColors>(ThemeContext);

  // タブのアクティブ・インアクティブカラーを設定
  const tabBarOptions: BottomTabBarOptions = {
    activeTintColor: theme.secondary,
    inactiveTintColor: palette.gray,
  };

  return (
    <Tab.Navigator
      screenOptions={({ route }) => getScreenOptions(route)}
      tabBarOptions={tabBarOptions}
    >
      <Tab.Screen name="Home" component={Home} />
      <Tab.Screen name="UserDetail" component={UserDetail} initialParams={{ id: '42' }} />
    </Tab.Navigator>
  );
};
  • createBottomTabNavigator で下タブのナビゲーションを作成できる
  • tabBarOptions でアクティブカラーなどを設定できる
  • タブアイコンやアクティブ状態などは、専用のレンダリング関数を作って変更する
    • Tab.navigator#screenOptions へ route を引数とした関数を登録できる
    • 受け取った route と、tabBarIcon から受け取れる color, size などを使い、アイコンのレンダリングを行う

ナビゲーションのネスト

export const Navigation = () => (
  <NavigationContainer>
    <Stack.Navigator initialRouteName="Main" screenOptions={{ headerShown: false }}>
      <Stack.Screen name="Modal" component={Modal} />
      <Stack.Screen name="Main" component={Main} />
      <Stack.Screen name="Auth" component={Auth} />
    </Stack.Navigator>
  </NavigationContainer>
);
  • Root: StackNav
    • Modal
    • Auth: StackNav
      • SignIn
      • SignUp
    • Main: BottomTabNav
      • Home
      • UserDetail

こんな感じでネストしてナビゲーションを作成できる。

  • 子ナビゲーションごとにヒストリが作成される
  • 子ナビゲーション間で情報の共有はない
  • 子ナビゲーションでハンドルできなかったナビは、親にバブリングされる
  • 親ナビゲーションの情報は、子ナビゲーションには伝達されない
  • 親子それぞれにナビゲーション UI があった場合は、重複してレンダリングされる

ネストしたスクリーンへ props を渡したい場合は、screen, params を指定すれば良い。さらにネストした指示もできる。

navigation.navigate('Root', {
  screen: 'Settings',
  params: { user: 'jane' },
});

references

Writings

blogsnippetcourse
Home©︎ suzukalight