React Native と TypeScript 2026年版:型安全なアーキテクチャと面接対策

2026年のReact NativeにおけるTypeScriptの型安全アーキテクチャを解説。Codegen、TurboModules、Strict TypeScript API、型付きナビゲーション、面接質問をコード例とともに紹介します。

React Native TypeScriptの型安全アーキテクチャとコード例、モバイルデバイス

2026年半ばの時点で、React NativeにおけるTypeScriptの型安全性は大きく成熟している。バージョン0.86ではReact 19.1、Strict TypeScript API、そしてCodegenを基盤とした完全なブリッジレスアーキテクチャが標準搭載されている。TypeScriptは、JavaScriptとネイティブコードをつなぐ単なるオプションのツールではなくなった。コンポーネントのpropsからTurboModuleのインターフェースに至るまで、アプリケーション全体の契約を駆動し、本番環境のクラッシュログではなくビルド時に不整合を検出する仕組みとなっている。

2026年の主な変更点

React Native 0.80でオプトイン方式のStrict TypeScript APIが導入され、0.82でレガシーブリッジが完全に廃止、0.85で最後のinteropレイヤーが削除された。2026年6月時点の0.86では、すべての新規プロジェクトがソースコードから直接生成されたTypeScript型を使用し、完全なブリッジレス構成で開始される。

TypeScriptがReact Nativeのデフォルトになった理由

バージョン0.76以降、npx react-native initコマンドで生成されるプロジェクトはすべてTypeScriptで構成されている。しかし、本質的な変化はネイティブ境界で起きた。Codegen導入以前、開発者はJavaScriptからObjective-CやKotlinへの境界を越える際に手動の型アサーションを記述していた。これは文字列ベースの暗黙的な契約であり、実行時に無言で破綻するものだった。

CodegenはTypeScriptの仕様ファイルを読み取り、C++、Objective-C++、Java/Kotlinのインターフェースを自動生成する。TypeScriptの仕様がnumberを返すメソッドを宣言すれば、生成されたネイティブインターフェースがその制約をコンパイル時に強制する。

React Nativeの公式ドキュメントでは基本的なセットアップが解説されているが、プロダクション環境で重要な型安全パターンはさらに先を行く。型付きナビゲーションスタック、ジェネリックAPIフック、状態マシン用のタグ付きユニオン、そしてCodegen駆動のTurboModule仕様がその中核を成す。

Strict TypeScript APIのセットアップ

React Native 0.80で導入されたStrict TypeScript APIは、ソースコードから直接生成された型のみに公開APIサーフェスを制限する。これにより、マイナーバージョン間で破壊される可能性のある内部モジュールへの偶発的な依存を防止できる。

tsconfig.jsonjson
{
  "compilerOptions": {
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "types": ["react-native/types/strict"]
  },
  "extends": "@react-native/typescript-config/tsconfig.json"
}

この設定により、react-native/Libraries/Text/Textのようなサブパスからのインポートは型エラーとなる。すべてのインポートはルートのreact-nativeパッケージから行う必要があり、0.80以降で強制されているディープインポートの非推奨化に準拠する形となる。

React Navigation 7による型安全なナビゲーション

React Navigation 7.xは、TypeScriptのファーストクラスサポートを提供している。基本パターンは、RootStackParamList型を定義して各画面名とそのパラメータをマッピングし、その型をナビゲーターと画面コンポーネントに伝播させるというものである。

navigation/types.tstypescript
export type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Settings: undefined;
  ArticleDetail: { articleId: string; source: 'feed' | 'search' };
};

export type AppTabParamList = {
  Dashboard: undefined;
  Explore: { category?: string };
  Notifications: undefined;
};

画面コンポーネントは、手動キャストなしで型付きpropsを受け取る。

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

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

export function ArticleDetailScreen({ route, navigation }: Props) {
  // route.params.articleId は string — 型によって保証されている
  // route.params.source は 'feed' | 'search' — ランタイムチェック不要
  const { articleId, source } = route.params;

  // navigation.navigate('Profile', { userId: '123' }) — 型チェック済み
  // navigation.navigate('Profile', {}) — コンパイルエラー: userId が未指定
  return (
    <ArticleView id={articleId} referrer={source} />
  );
}

このパターンにより、ランタイムエラーの一分類が完全に排除される。誤ったパラメータや不足したパラメータで画面へ遷移しようとすると、アプリ実行前にビルド時に失敗する。

useNavigationフックの型付け

直接の画面子コンポーネントではないコンポーネントでは、useNavigation<NativeStackNavigationProp<RootStackParamList>>()を使用することで、propsの受け渡しなしに同等の型安全性を得られる。

Codegenによる型安全なTurboModuleの構築

TurboModulesは、旧来のNative Modulesシステムに取って代わるものである。TypeScriptの仕様ファイルが唯一の信頼できる情報源として機能し、Codegenがそこからネイティブインターフェースを生成する。仕様とネイティブ実装が乖離した場合、ビルドが失敗する。

specs/NativeDeviceInfo.tstypescript
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getDeviceModel(): string;
  getBatteryLevel(): Promise<number>;
  getStorageInfo(): Promise<{
    totalBytes: number;
    freeBytes: number;
    usedPercentage: number;
  }>;
  onBatteryChange(callback: (level: number) => void): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');

npx react-native codegenを実行すると、対応するC++、Objective-C++、Javaインターフェースが生成される。ネイティブ実装はすべてのメソッドシグネチャと正確に一致する必要がある。例えば、getStorageInfoは3つの数値フィールドを持つオブジェクトを返す必要があり、異なる構造を返すとネイティブ側でコンパイルエラーが発生する。

android/app/src/main/java/com/app/DeviceInfoModule.ktkotlin
class DeviceInfoModule(reactContext: ReactApplicationContext) :
    NativeDeviceInfoSpec(reactContext) {

    // 戻り値の型は生成されたNativeDeviceInfoSpecによって強制される
    override fun getDeviceModel(): String {
        return Build.MODEL
    }

    override fun getBatteryLevel(): Promise<Double> {
        val bm = reactContext.getSystemService(Context.BATTERY_SERVICE)
            as BatteryManager
        val level = bm.getIntProperty(
            BatteryManager.BATTERY_PROPERTY_CAPACITY
        ).toDouble()
        return Promise.resolve(level)
    }
}

旧アーキテクチャで問題となっていたReadableMapNSDictionaryのパース処理による暗黙の型変換バグが、このアプローチによって排除される。

React Nativeの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

ジェネリック型を活用した型安全なAPIフック

再利用可能な型付きフックパターンにより、画面間でフェッチロジックを重複させることなく、完全な型推論を維持できる。

hooks/useApiQuery.tstypescript
import { useQuery, UseQueryOptions } from '@tanstack/react-query';

interface ApiResponse<T> {
  data: T;
  meta: { page: number; totalPages: number };
}

export function useApiQuery<T>(
  key: readonly string[],
  endpoint: string,
  options?: Omit<UseQueryOptions<ApiResponse<T>>, 'queryKey' | 'queryFn'>
) {
  return useQuery<ApiResponse<T>>({
    queryKey: key,
    queryFn: async () => {
      const response = await fetch(`${API_BASE}${endpoint}`);
      if (!response.ok) throw new ApiError(response.status);
      return response.json() as Promise<ApiResponse<T>>;
    },
    ...options,
  });
}

// 使用例 — T は Article[] として推論される
interface Article {
  id: string;
  title: string;
  publishedAt: string;
}

const { data, isLoading } = useApiQuery<Article[]>(
  ['articles', 'latest'],
  '/articles?sort=latest'
);
// data.data は Article[] — 完全に型付き
// data.meta.totalPages は number

ジェネリックパラメータTは、フックの呼び出し元からクエリ関数、結果を利用するコンポーネントに至るまで、チェーン全体に伝播する。asによるキャストもany型も不要である。

判別共用体による状態マシンの設計

画面の複雑な状態(読み込み中、エラー、空、読み込み完了)は、オプショナルフィールドの集合ではなく、判別共用体としてモデル化するのが最適である。

types/screen-state.tstypescript
type ScreenState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'error'; error: string; retryCount: number }
  | { status: 'empty'; message: string }
  | { status: 'loaded'; data: T; refreshedAt: Date };

// components/DataScreen.tsx
function renderContent<T>(state: ScreenState<T>, renderItem: (data: T) => ReactNode) {
  switch (state.status) {
    case 'idle':
      return null;
    case 'loading':
      return <LoadingSpinner />;
    case 'error':
      // state.error はここでは string — TypeScriptが自動的に絞り込む
      return <ErrorBanner message={state.error} retries={state.retryCount} />;
    case 'empty':
      return <EmptyState message={state.message} />;
    case 'loaded':
      // state.data は T — 完全に型付き
      return renderItem(state.data);
  }
}

このパターンにより、不可能な状態を型レベルで表現不能にできる。loading状態が古いdataを誤って保持することはなく、error状態には常にデバッグのためのコンテキストが含まれる。

部分的な状態オブジェクトの落とし穴

よくあるアンチパターン:{ isLoading: boolean; error?: string; data?: T }。この定義では{ isLoading: true, error: 'fail', data: [...] }のような状態が許容される — 3つの矛盾するシグナルが同時に存在する。判別共用体を使えば、このような状態を型レベルで防止できる。

React Native TypeScript 面接質問集

New ArchitectureとTypeScriptが標準となった2026年のシニアモバイルエンジニアリングチームの面接で実際に問われる質問を取り上げる。

CodegenはJavaScript-ネイティブ境界でどのように型安全性を実現していますか?

CodegenはTypeScript(またはFlow)の仕様ファイルを読み取り、C++、Objective-C++、Java/Kotlinのインターフェースコードを生成する。生成されたネイティブインターフェースは、仕様で定義されたメソッドシグネチャ、パラメータ型、戻り値型を正確に強制する。ネイティブ実装が乖離した場合 — 仕様がDoubleを宣言しているのにIntを返す、構造体からフィールドを省略するなど — ネイティブコンパイラがビルドを拒否する。これにより、型エラーがランタイムクラッシュからビルド時の失敗に移行する。

Strict TypeScript APIとは何か、なぜ重要なのですか?

React Native 0.80で導入されたStrict TypeScript APIは、手書きの.d.tsファイルではなく、React Nativeのソースコードから直接型を生成する。インポートをルートのreact-nativeパッケージに制限し、ディープインポートを非推奨とする。これにより安定した公開APIサーフェスが定義され、利用者がstrict型のみを使用している限り、内部リファクタリングによって利用者のコードが破壊されることはない。tsconfig.json"types": ["react-native/types/strict"]を設定することで有効化できる。

ネストされたナビゲーター間でReact Navigationのパラメータをどのように型付けしますか?

ナビゲーターごとにParamList型を定義し、NavigatorScreenParamsを使用してそれらを合成する。スタック内にネストされたタブナビゲーターの場合、スタックのパラメータリストがタブのパラメータリストを参照する:type RootStack = { Main: NavigatorScreenParams<TabParamList>; Modal: { id: string } }。すべてのnavigate()呼び出しがネスト階層全体を通じて型チェックされ、画面名の誤りやパラメータの欠落がコンパイル時に検出される。

判別共用体はReact Nativeの状態管理でどのような問題を解決しますか?

判別共用体は、相互排他的な状態(loading、error、loaded)をユニオン型の別々の分岐として、statusフィールドをキーにしてモデル化する。TypeScriptはswitch文の各分岐で型を自動的に絞り込むため、state.dataへのアクセスはstate.status === 'loaded'の場合にのみ可能となる。これにより、読み込みインジケーターがエラーデータと同時に表示されるといったバグを防止できる。オプショナルフィールドやbooleanフラグでは、このクラスのバグを防ぐことはできない。

TurboModulesと旧来のNative Modulesシステムの違いを説明してください。

Native Modulesは非同期ブリッジを介して通信し、すべてのデータをJSONにシリアライズしていた。TurboModulesはJSI(JavaScript Interface)を使用して同期的かつ直接的なC++呼び出しを行い、シリアライズのオーバーヘッドがない。また、遅延読み込み(アプリ起動時ではなく初回使用時に読み込む)によりコールドスタート時間を削減し、Codegenを使用してTypeScript仕様から型安全なインターフェースを生成する。旧システムはReadableMap / NSDictionaryのパースによるランタイム型変換に依存していたが、TurboModulesはコンパイル時に型を強制する。

React Nativeの面接質問では、JSI、Fabric、ブリッジレスアーキテクチャについてさらに詳しく解説している。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

まとめ

  • Strict TypeScript API(0.80以降)は安定した公開サーフェスにインポートを制限し、内部変更による破壊を防止する — すべての新規プロジェクトで有効化すべきである
  • Codegenは TypeScript仕様ファイルからネイティブインターフェースを生成し、JS-ネイティブ境界における型エラーをランタイムクラッシュからビルド時の失敗に移行させる
  • RootStackParamListNativeStackScreenPropsを使った型付きナビゲーションパラメータにより、画面名の誤りやパラメータの欠落をアプリ実行前に検出できる
  • 判別共用体は画面状態を相互排他的な分岐としてモデル化し、不可能な状態を型レベルで表現不能にする
  • 型付き仕様を持つTurboModulesは、旧来のReadableMap / NSDictionaryパースを置き換え、JavaScriptからC++、プラットフォームネイティブコードまで完全な型安全性を実現する
  • TanStack Queryを使ったジェネリックAPIフックは、エンドポイントからコンポーネントまで手動キャストなしで型推論を維持する

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#react-native
#typescript
#mobile-development
#new-architecture
#turbomodules

共有

関連記事