2026年版 React Nativeアプリ開発完全ガイドと技術面接対策

React Native 0.87のNew Architecture、JSI、Fabric、TurboModulesを使った最新のモバイルアプリ開発手法を解説。面接でよく出る質問と回答も網羅。

2026年版 React Nativeアプリ開発完全ガイドと技術面接対策

2026年のReact Nativeアプリ開発は、バージョン0.87でNew Architectureが唯一のサポート対象となり、根本的に変化しました。JavaScriptからネイティブへの呼び出しをJSON形式でシリアライズしていた従来のブリッジは廃止され、直接同期通信を可能にするJSI(JavaScript Interface)に置き換わっています。

React Native 0.87の変更点

React Native 0.87ではNew Architectureが必須となりました。旧ブリッジは完全に削除されています。ベンチマークテストでは、複雑なリストレンダリングが43%高速化、スクロール時のフレームドロップが95%減少、メモリ使用量が33%削減されました。

JSIとNew Architectureの仕組み

JSI(JavaScript Interface)は、非同期ブリッジをJavaScriptとネイティブコード間の直接C++バインディングに置き換えます。データをJSONにシリアライズし、メッセージキューを通過させ、反対側でデシリアライズする代わりに、JSIはJavaScriptがネイティブオブジェクトへの参照を保持し、そのメソッドを直接呼び出すことを可能にします。

このアーキテクチャの変更により、3つのコア機能が実現されます。同期的なネイティブ呼び出し、JavaScriptとネイティブレイヤー間でのオブジェクトの共有所有権、そしてネイティブモジュールの遅延読み込みです。

NativeUserModule.tstypescript
import { TurboModuleRegistry, TurboModule } from 'react-native';

export interface Spec extends TurboModule {
  getUserProfile(userId: string): Promise<{
    id: string;
    name: string;
    email: string;
  }>;
  
  // Synchronous call - only possible with JSI
  getDeviceLocale(): string;
}

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

getDeviceLocale()メソッドはPromiseを待つことなく即座に値を返します。これは、すべてのネイティブ呼び出しが非同期のシリアライズを必要とした旧ブリッジでは不可能でした。

Fabricを使用した本番環境対応コンポーネントの構築

FabricはJSIと連携して動作する新しいレンダリングシステムです。C++でUIツリーを管理し、JavaScriptスレッドと直接同期することで、旧ブリッジが高速な更新に追いつけなくなった際に発生していた「UIのカクツキ」を解消します。

ProductCard.tsxtypescript
import React, { memo, useCallback } from 'react';
import {
  View,
  Text,
  Image,
  Pressable,
  StyleSheet,
} from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  inStock: boolean;
}

interface ProductCardProps {
  product: Product;
  onPress: (id: string) => void;
}

const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

export const ProductCard = memo(function ProductCard({
  product,
  onPress,
}: ProductCardProps) {
  const scale = useSharedValue(1);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  const handlePressIn = useCallback(() => {
    scale.value = withSpring(0.95);
  }, [scale]);

  const handlePressOut = useCallback(() => {
    scale.value = withSpring(1);
  }, [scale]);

  const handlePress = useCallback(() => {
    onPress(product.id);
  }, [onPress, product.id]);

  return (
    <AnimatedPressable
      style={[styles.card, animatedStyle]}
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={handlePress}
      accessibilityRole="button"
      accessibilityLabel={`${product.name}, ${product.price} dollars`}
    >
      <Image
        source={{ uri: product.imageUrl }}
        style={styles.image}
        resizeMode="cover"
      />
      <View style={styles.content}>
        <Text style={styles.name} numberOfLines={2}>
          {product.name}
        </Text>
        <Text style={styles.price}>${product.price.toFixed(2)}</Text>
        {!product.inStock && (
          <Text style={styles.outOfStock}>Out of Stock</Text>
        )}
      </View>
    </AnimatedPressable>
  );
});

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    overflow: 'hidden',
    marginHorizontal: 8,
    marginVertical: 4,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  image: {
    width: '100%',
    height: 160,
  },
  content: {
    padding: 12,
  },
  name: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
    marginBottom: 4,
  },
  price: {
    fontSize: 18,
    fontWeight: '700',
    color: '#2563eb',
  },
  outOfStock: {
    fontSize: 12,
    color: '#dc2626',
    marginTop: 4,
  },
});

このコンポーネントは、JSIを介してUIスレッド上でアニメーションを実行するreact-native-reanimatedを使用しています。useSharedValueuseAnimatedStyleフックは、ブリッジを経由せずにネイティブコードと直接通信します。

TanStack Queryによる状態管理

React Nativeアプリケーションにおけるサーバー状態は、専用のツールを使用することで効率的に管理できます。TanStack Queryは、グローバルな状態コンテナを必要とせずに、キャッシュ、バックグラウンドでの再フェッチ、楽観的更新を処理します。

hooks/useProducts.tstypescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  inStock: boolean;
}

const API_BASE = 'https://api.example.com';

async function fetchProducts(category?: string): Promise<Product[]> {
  const url = category
    ? `${API_BASE}/products?category=${category}`
    : `${API_BASE}/products`;
  
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error('Failed to fetch products');
  }
  return response.json();
}

export function useProducts(category?: string) {
  return useQuery({
    queryKey: ['products', category],
    queryFn: () => fetchProducts(category),
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 30 * 60 * 1000, // 30 minutes
  });
}

export function useToggleStock() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (productId: string) => {
      const response = await fetch(`${API_BASE}/products/${productId}/toggle-stock`, {
        method: 'POST',
      });
      if (!response.ok) {
        throw new Error('Failed to update stock');
      }
      return response.json();
    },
    // Optimistic update
    onMutate: async (productId) => {
      await queryClient.cancelQueries({ queryKey: ['products'] });
      
      const previousProducts = queryClient.getQueryData<Product[]>(['products']);
      
      queryClient.setQueryData<Product[]>(['products'], (old) =>
        old?.map((p) =>
          p.id === productId ? { ...p, inStock: !p.inStock } : p
        )
      );
      
      return { previousProducts };
    },
    onError: (_err, _productId, context) => {
      if (context?.previousProducts) {
        queryClient.setQueryData(['products'], context.previousProducts);
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });
}

onMutateコールバックはサーバーが応答する前に楽観的更新を実行します。ミューテーションが失敗した場合、onErrorで以前の状態を復元します。このパターンにより、データの一貫性を維持しながら即座にUIフィードバックを提供できます。

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

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

Expo Routerによるナビゲーション

Expo RouterはReact Nativeにファイルベースのルーティングを提供し、Next.jsのApp Routerと同様の仕組みを実現しています。ファイル構造が自動的にルートを定義します。

app/(tabs)/_layout.tsxtypescript
import { Tabs } from 'expo-router';
import { Home, Search, ShoppingCart, User } from 'lucide-react-native';

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#2563eb',
        tabBarInactiveTintColor: '#6b7280',
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Home color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Search',
          tabBarIcon: ({ color, size }) => (
            <Search color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="cart"
        options={{
          title: 'Cart',
          tabBarIcon: ({ color, size }) => (
            <ShoppingCart color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <User color={color} size={size} />
          ),
        }}
      />
    </Tabs>
  );
}

動的ルートはブラケット記法を使用します。app/product/[id].tsxに配置されたファイルは、/product/123のようなパスを処理します。

app/product/[id].tsxtypescript
import { useLocalSearchParams } from 'expo-router';
import { View, Text, ActivityIndicator } from 'react-native';
import { useQuery } from '@tanstack/react-query';

export default function ProductDetailScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();

  const { data: product, isLoading, error } = useQuery({
    queryKey: ['product', id],
    queryFn: () => fetchProduct(id),
    enabled: !!id,
  });

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" color="#2563eb" />
      </View>
    );
  }

  if (error || !product) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Product not found</Text>
      </View>
    );
  }

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Text style={{ fontSize: 24, fontWeight: 'bold' }}>{product.name}</Text>
      <Text style={{ fontSize: 20, color: '#2563eb' }}>
        ${product.price.toFixed(2)}
      </Text>
    </View>
  );
}

React Nativeアーキテクチャに関する面接頻出質問

2026年のReact Nativeポジションの技術面接では、New Architectureが重点的に問われます。以下の質問が頻繁に出題されます。

面接の重点分野

面接官は、候補者がJSI、Fabric、TurboModulesを概念レベルで説明できることを期待しています。構文を暗記することよりも、なぜアーキテクチャが変わったのか、どのような問題を解決するのかを理解することが重要です。

質問:JSIとは何か、なぜReact Nativeはそれを採用したのか?

JSI(JavaScript Interface)は、JavaScriptがネイティブオブジェクトへの参照を保持し、そのメソッドを直接呼び出すことを可能にするC++レイヤーです。旧ブリッジはすべての呼び出しをJSONにシリアライズし、非同期でキューに入れ、ネイティブ側でデシリアライズしていました。これによりレイテンシが発生し、同期操作ができませんでした。

JSIを使用することで、JavaScript関数はネイティブメソッドを呼び出し、同じフレーム内で結果を受け取ることができます。これにより、同期的なレイアウト測定やネイティブビューの直接操作などの機能が可能になります。

質問:Fabricは旧レンダラーとどう違うのか?

Fabricは、JavaScriptではなくネイティブコードでUIツリーを保持するC++レンダリングシステムです。旧レンダラーはシャドウツリーをJavaScriptで保持し、更新バッチをブリッジ経由で送信していました。

Fabricの利点:

  • JSIを介した同期的なレイアウト計算
  • 並行レンダリングのサポート(React 18の機能が正しく動作)
  • JSとネイティブ間の共有所有権によるメモリ管理の改善
  • シリアライズのオーバーヘッド削減

質問:TurboModulesとは何か?

TurboModulesはNative Modulesの後継です。主な違いは以下の通りです。

  1. 遅延読み込み:TurboModulesは最初にアクセスされたときのみ読み込まれ、起動時間を短縮
  2. 型安全性:CodeGenがTypeScript仕様から型安全なバインディングを生成
  3. 同期メソッド:TurboModulesは必要に応じて同期関数を公開可能
  4. 直接JSIアクセス:ブリッジのシリアライズが不要

質問:同期的なネイティブメソッドを使用するのはどのような場合か?

同期的なネイティブメソッドが適しているのは以下の操作です。

  • マイクロ秒単位で完了する処理(デバイスロケールの読み取り、パーミッション状態の確認)
  • 意図的にUIをブロックする場合(ナビゲーション前のモーダル確認)
  • 即座のレンダリングに必要な値を返す場合

同期メソッドを避けるべきケース:

  • ネットワークリクエスト
  • 小規模な読み取りを超えるファイルI/O
  • データベースクエリ
  • 16msを超えるすべての操作(フレームをブロックする)

React Nativeの面接対策については、ネットワークとAPIモジュールネイティブモジュールセクションも参照してください。

React Nativeコンポーネントのテスト

React Native Testing Libraryは、実装の詳細ではなく、アクセシビリティと動作に焦点を当て、ユーザーが操作するようにコンポーネントをテストするためのユーティリティを提供します。

__tests__/ProductCard.test.tsxtypescript
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { ProductCard } from '../components/ProductCard';

const mockProduct = {
  id: 'prod-1',
  name: 'Wireless Headphones',
  price: 149.99,
  imageUrl: 'https://example.com/headphones.jpg',
  inStock: true,
};

describe('ProductCard', () => {
  it('displays product information', () => {
    const onPress = jest.fn();
    render(<ProductCard product={mockProduct} onPress={onPress} />);

    expect(screen.getByText('Wireless Headphones')).toBeOnTheScreen();
    expect(screen.getByText('$149.99')).toBeOnTheScreen();
  });

  it('calls onPress with product id when pressed', () => {
    const onPress = jest.fn();
    render(<ProductCard product={mockProduct} onPress={onPress} />);

    fireEvent.press(
      screen.getByRole('button', { name: /wireless headphones/i })
    );

    expect(onPress).toHaveBeenCalledWith('prod-1');
    expect(onPress).toHaveBeenCalledTimes(1);
  });

  it('shows out of stock message when product unavailable', () => {
    const outOfStockProduct = { ...mockProduct, inStock: false };
    render(<ProductCard product={outOfStockProduct} onPress={jest.fn()} />);

    expect(screen.getByText('Out of Stock')).toBeOnTheScreen();
  });
});

テストはテストIDやコンポーネントの内部ではなく、テキストコンテンツとアクセシビリティロールでクエリを行います。このアプローチは、リファクタリングに対する耐性を維持しながら、ユーザーに影響を与えるリグレッションを検出します。

本番環境でのパフォーマンスモニタリング

React Native 0.87はSentryなどのツールと統合し、本番環境でのパフォーマンスモニタリングを実現しています。追跡すべき主要なメトリクス:

  • Time to Interactive(TTI):アプリがユーザー入力に応答するまでの時間
  • フレームドロップ:レンダリングに16.67ms以上かかるフレーム
  • JavaScriptスレッド使用率:高い使用率はUIをブロックする高コストな計算を示す
  • メモリ増加:継続的な増加はリークを示唆する
app/_layout.tsxtypescript
import * as Sentry from '@sentry/react-native';
import { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native';

Sentry.init({
  dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,
  profilesSampleRate: 0.1,
  enableAutoPerformanceTracing: true,
});

export default function RootLayout() {
  useEffect(() => {
    const subscription = AppState.addEventListener(
      'change',
      (state: AppStateStatus) => {
        if (state === 'active') {
          Sentry.addBreadcrumb({
            category: 'app.lifecycle',
            message: 'App became active',
            level: 'info',
          });
        }
      }
    );

    return () => subscription.remove();
  }, []);

  // ... rest of layout
}

2026年のReact Native開発における重要ポイント

  • New Architecture(JSI、Fabric、TurboModules)はReact Native 0.87で必須です。レガシーブリッジは存在しません。
  • JSIは同期的なネイティブ呼び出しを可能にします。16ms未満の操作に限定して使用してください。
  • FabricはC++でUIをレンダリングし、JSIを介してJavaScriptと同期します。これにより非同期レンダリングのボトルネックが解消されます。
  • TurboModulesは遅延読み込みされ、TypeScript仕様から生成された型安全なバインディングを提供します。
  • Expo RouterはNext.jsのApp Routerと同様のファイルベースナビゲーションを提供します。
  • React Native Testing Libraryは、パブリックAPIとアクセシビリティツリーを通じてコンポーネントをテストします。
  • 本番アプリは初日からTTI、フレームドロップ、メモリ使用量を追跡する必要があります。

完全なアプリケーション構築の詳細については、React Native完全アプリチュートリアルを参照してください。New Architectureガイドでは、古いバージョンからの移行について解説しています。

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

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

今日のチャレンジ

React Native のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月6日 更新

共有

関連記事