【React Navigation】もう画面遷移で落とさない!TypeScriptで100%型安全にする実装ガイド

エンジニアの記録
技術メモ・実体験

navigation.navigate('Detail', { id }) の引数がanyで補完が効かない。存在しない画面名を書いてもエラーにならず、実行して初めて落ちる。型を入れていないとこうなります。

この記事でわかること

React NavigationにParamListの型を入れ、画面名・パラメータ・route.paramsまで型安全にする手順をコードつきで書きます。

PROFILE 20代ITエンジニア|React Native(Expo)× AWS Amplifyでアプリ開発中|Raw Ambition 運営
キラ
キラ
ParamListを一個定義するだけで、画面遷移のミスがビルド前に全部わかるようになる。やらない理由がない。
Contents
  1. 結論:ParamListを定義する
  2. なぜanyになるのか
  3. ParamListをNavigatorに渡す
  4. 画面コンポーネントでの型付け
  5. useNavigation / useRouteの型付け
  6. まとめ
結論:ParamListを定義する

結論です。各スタックの「画面名→パラメータ」の対応表(ParamList)を型で定義し、それをNavigatorとフックに渡す。これだけで navigateroute.params も型安全になります。

なぜanyになるのか

React Navigationは「どの画面にどんなパラメータが渡るか」を型で知りません。こちらがParamListを教えない限り、すべてany扱いになります。だから存在しない画面名もパラメータの抜けも検出できないのです。

ParamListをNavigatorに渡す

まず画面とパラメータの対応を型で書きます。パラメータがない画面はundefinedにします。

// navigation/types.ts
export type RootStackParamList = {
  Home: undefined;                 // パラメータなし
  Detail: { id: string };          // idが必須
  Edit: { id: string; mode?: 'new' | 'update' };
};

この型をジェネリクスでNavigatorに渡します。

import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { RootStackParamList } from './types';

const Stack = createNativeStackNavigator<RootStackParamList>();

// 存在しない画面名やtypoはここでエラーになる
<Stack.Navigator>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
画面コンポーネントでの型付け

画面側は NativeStackScreenProps を使うと、navigationrouteの両方が一度に型付けされます。

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

type Props = NativeStackScreenProps<RootStackParamList, 'Detail'>;

function DetailScreen({ route, navigation }: Props) {
  const { id } = route.params;     // id: string として補完される

  return (
    <Button
      title="編集"
      onPress={() => navigation.navigate('Edit', { id })}
    />
  );
}

? POINT

これでnavigate('Edit')のようにパラメータを忘れるとコンパイルエラーになります。「実行したらundefinedで落ちた」が起きなくなるのが最大のメリットです。
useNavigation / useRouteの型付け

propsを受け取らない深い子コンポーネントでは、フックにジェネリクスを渡します。

import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../navigation/types';

const navigation =
  useNavigation<NativeStackNavigationProp<RootStackParamList>>();

const route = useRoute<RouteProp<RootStackParamList, 'Detail'>>();

毎回ジェネリクスを書くのが面倒なら、declare globalでデフォルト型を登録すると、引数なしのuseNavigation()でも型が効きます。

// types/navigation.d.ts
import { RootStackParamList } from '../navigation/types';

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}
スカイ
スカイ
declare globalまでやると、プロジェクト全体でnavigateが守られる感じになるね。

? この記事のまとめ
  • 「画面名→パラメータ」のParamListを型で定義する
  • createNativeStackNavigatorにジェネリクスで渡す
  • 画面はNativeStackScreenProps、子はフックにジェネリクス
  • declare globalで全体にデフォルト型を効かせると楽
Recommended
学んだことは、ブログに書くと一番定着する
エンジニアのアウトプット先として、表示速度の速いConoHa WINGでブログを始めました。月額わずか、最短10分で開設できます。
▶ ConoHa WINGでブログを始める
※ アフィリエイトリンクです
Next
次回:DynamoDBのGSI設計で「最新N件」を効率的に取る
AWS実践シリーズ

コメント

タイトルとURLをコピーしました