ナビゲーションライブラリ「React Navigation」徹底解説

どうもこんにちは、たくびー(@takubii)です。
React nativeで画面遷移をする上でとても便利なReact navigationについて解説したいと思います。
React navigation自体はReact native専用ではありませんが、多彩な遷移方法を提供してくれるのでとても便利なライブラリです。
今回はその中でも最も基本的な画面遷移について取り上げます。

## 開発環境
今回のコードは以下の環境で実施しています。

- React native 0.72.6
- React navigation 6.1.8

## React navigationのインストール

React nativeの環境はすでに作成している前提で進めていきます。

まずはReact navigationのインストールを行います。

```zsh:terminal
npm install @react-navigation/native
```

依存するライブラリのインストールを行います。

```zsh:terminal
npm install react-native-screens react-native-safe-area-context
```

最後にPodインストールを行います。

```zsh:terminal
cd ios && pod install && cd ..
```

詳しくは以下の公式サイトを参照してください。

https://reactnavigation.org/docs/getting-started


## ソースコードの実装

今回はStackナビゲーションを使用します。
この動作はページを遷移させるもので、ヘッダーには戻るボタンが表示され、スマートフォンではジェスチャーにより前の画面に戻ることができます。

React navigationでは使用するNavigatorごとにインストールが必要です。そのため以下のパッケージをインストールします。

```zsh:terminal
npm install @react-navigation/native-stack
```

今回は認証画面から認証し、Home画面には詳細画面へのリンクがあるというシナリオで実装を行います。
まずは認証状態を管理するContextを作ります。

1. 認証状態を管理するContextの作成

`AuthContext.tsx`:

```typescript:AuthContext.tsx
import React, {ReactNode, createContext, useContext, useState} from 'react';

type AuthContextType = {
  isAuthenticated: boolean;
  setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>>;
};

type AuthProviderProps = {
  children: ReactNode;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider: React.FC<AuthProviderProps> = ({children}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  return (
    <AuthContext.Provider value={{isAuthenticated, setIsAuthenticated}}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = (): AuthContextType => {
  const context = useContext(AuthContext);

  if (!context) {
    throw new Error('useAuthはAuthProviderと一緒に使ってください。');
  }

  return context;
};
```

その後、認証画面、ホーム画面、詳細画面を作成します。

2. 認証画面、ホーム画面、詳細画面の作成:

`AuthScreen.tsx`:

```typescript:AuthScreen.tsx
import React from 'react';
import {Button, StyleSheet, View} from 'react-native';

import {useAuth} from '../hooks/AuthContext';

export const AuthScreen: React.FC = () => {
  const {setIsAuthenticated} = useAuth();

  return (
    <View style={styles.container}>
      <Button title="ログイン" onPress={() => setIsAuthenticated(true)} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
```

`HomeScreen.tsx`:

```typescript:HomeScreen.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

import {NativeStackScreenProps} from '@react-navigation/native-stack';

import {RootStackParams} from '../App';
import {Header} from '../components/Header';
import {useAuth} from '../hooks/AuthContext';

export const HomeScreen: React.FC<
  NativeStackScreenProps<RootStackParams, 'Home'>
> = ({navigation}) => {
  const {setIsAuthenticated} = useAuth();

  return (
    <View style={styles.container}>
      <Header />
      <View style={styles.main}>
        <Text>ホーム画面</Text>
        <Button
          title="詳細画面へ"
          onPress={() => navigation.navigate('Details')}
        />
        <Button title="ログアウト" onPress={() => setIsAuthenticated(false)} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
  },
  main: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
```

`DetailsScreen.tsx`:

```typescript:DetailScreen.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

import {NativeStackScreenProps} from '@react-navigation/native-stack';

import {RootStackParams} from '../App';
import {Header} from '../components/Header';

export const DetailsScreen: React.FC<
  NativeStackScreenProps<RootStackParams, 'Details'>
> = ({navigation}) => {
  return (
    <View style={styles.container}>
      <Header />
      <Text>詳細画面</Text>
      <Button title="戻る" onPress={() => navigation.goBack()} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
```

最後に`App.tsx`にNavigatorの設定を行います。

3. ナビゲーションと認証フローの制御:

`App.tsx`:

```typescript:App.tsx
import React from 'react';

import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';

import {AuthProvider, useAuth} from './hooks/AuthContext';
import {AuthScreen} from './screens/AuthScreen';
import {DetailsScreen} from './screens/DetailsScreen';
import {HomeScreen} from './screens/HomeScreen';

export type RootStackParams = {
  Auth: undefined;
  Home: undefined;
  Details: undefined;
};

const Stack = createNativeStackNavigator<RootStackParams>();

const App: React.FC = () => {
  return (
    <AuthProvider>
      <NavigationContainer>
        <NavigationContent />
      </NavigationContainer>
    </AuthProvider>
  );
};

const NavigationContent: React.FC = () => {
  const {isAuthenticated} = useAuth();

  return (
    <Stack.Navigator>
      {!isAuthenticated ? (
        <>
          <Stack.Screen name="Auth" component={AuthScreen} />
        </>
      ) : (
        <>
          <Stack.Screen name="Home" component={HomeScreen} />
          <Stack.Screen name="Details" component={DetailsScreen} />
        </>
      )}
    </Stack.Navigator>
  );
};

export default App;
```

## まとめ

React navigationについて簡単に紹介をしました。
Navigatorには様々な種類があり、ハンバーガーメニューのようなものから、タブリストでの切り替えなどもできるようです。
また、それらを組み合わせることも可能なので画面遷移で凝ったことをやりたい時にもとても役立ってくれると思います。

それでは今回はこの辺りで締めたいと思います。
この記事を最後まで読んでいただき、ありがとうございます。
また機会があればお会いしましょう。