Skip to content
Published on

React Native vs Flutter 2025:クロスプラットフォームモバイル開発の全て

Authors

目次(もくじ)

1. 2025年(ねん)クロスプラットフォームモバイル開発(かいはつ)の展望(てんぼう)

市場(しじょう)の現状(げんじょう)

2025年、モバイル開発市場でクロスプラットフォームフレームワークのシェアは増加(ぞうか)し続(つづ)けています。ネイティブ開発と比(くら)べたコスト削減(さくげん)と迅速(じんそく)な市場投入が主要な動機です。

フレームワークGitHub Stars週間ダウンロード/使用主要企業
React Native120k+npm 2M+/週Meta、Microsoft、Shopify
Flutter165k+pub.dev活発Google、BMW、Alibaba
KMP (Compose)成長中JetBrainsエコシステムNetflix、Cash App

主要(しゅよう)な選択基準(きじゅん)

  • チームの技術スタック: Web開発チームならReact Native、Dart学習意欲があればFlutter
  • UIカスタマイズ: プラットフォームネイティブの見た目ならRN、完全カスタムUIならFlutter
  • パフォーマンス要件: アニメーション集約的ならFlutter、一般アプリなら両方十分
  • 既存コード: Webコード再利用ならRN、新規プロジェクトならFlutterも良い選択
  • 採用市場: JavaScript開発者プールが広くRNが採用に有利

2. React Native 2025

New Architecture(GA)

React NativeのNew Architectureは2024年にGA(Generally Available)となり、2025年には事実上(じじつじょう)の標準(ひょうじゅん)となりました。

核心(かくしん)コンポーネント:

  1. Fabric: 新レンダリングシステム。同期レンダリングでUI応答性向上
  2. TurboModules: ネイティブモジュールの遅延ロード。アプリ起動時間短縮
  3. JSI(JavaScript Interface): JavaScriptとネイティブコード間の直接通信。ブリッジ除去
  4. Codegen: 型安全なネイティブコードの自動生成
// TurboModule定義(New Architecture)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getConstants(): {
    platform: string;
    version: number;
  };
  multiply(a: number, b: number): Promise<number>;
}

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

Hermesエンジン

HermesはReact Nativeのデフォルトの JavaScript エンジンで、モバイル環境に最適化されています。

  • バイトコード事前コンパイル: アプリ起動時間の短縮
  • メモリ効率: ガベージコレクション最適化
  • サイズ削減: アプリバンドルサイズの縮小
  • Static Hermes(実験的): AOTコンパイルで追加パフォーマンス向上

React Nativeの核心パターン

// 関数コンポーネント + Hooks
import React, { useState, useCallback } from 'react';
import { View, Text, FlatList, StyleSheet, Pressable } from 'react-native';

interface User {
  id: string;
  name: string;
  email: string;
}

const UserListScreen: React.FC = () => {
  const [users, setUsers] = useState<User[]>([]);
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    try {
      const response = await fetch('https://api.example.com/users');
      const data = await response.json();
      setUsers(data);
    } finally {
      setRefreshing(false);
    }
  }, []);

  const renderItem = useCallback(({ item }: { item: User }) => (
    <Pressable style={styles.card}>
      <Text style={styles.name}>{item.name}</Text>
      <Text style={styles.email}>{item.email}</Text>
    </Pressable>
  ), []);

  return (
    <FlatList
      data={users}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      refreshing={refreshing}
      onRefresh={onRefresh}
    />
  );
};

const styles = StyleSheet.create({
  card: {
    padding: 16,
    marginHorizontal: 16,
    marginVertical: 8,
    backgroundColor: '#fff',
    borderRadius: 8,
    elevation: 2,
  },
  name: { fontSize: 18, fontWeight: '600' },
  email: { fontSize: 14, color: '#666', marginTop: 4 },
});

3. Flutter 2025

Impellerレンダリングエンジン

ImpellerはFlutterの新(あたら)しいレンダリングエンジンで、Skiaを置(お)き換(か)えます。

主要な改善点:

  • シェーダー事前コンパイル: 初回実行のjank(カクつき)を除去
  • 予測可能なパフォーマンス: フレームドロップの最小化
  • Metal/Vulkanネイティブ: プラットフォーム別GPU APIの直接活用
  • iOSではデフォルト有効、Androidでも安定版

Dart 3

Dart 3はFlutterの開発体験を大幅(おおはば)に向上(こうじょう)させました。

// パターンマッチング(Dart 3)
sealed class Result<T> {
  const Result();
}
class Success<T> extends Result<T> {
  final T data;
  const Success(this.data);
}
class Error<T> extends Result<T> {
  final String message;
  const Error(this.message);
}
class Loading<T> extends Result<T> {
  const Loading();
}

// switch式でパターンマッチング
Widget buildContent(Result<User> result) => switch (result) {
  Success(:final data) => UserCard(user: data),
  Error(:final message) => ErrorWidget(message: message),
  Loading() => const CircularProgressIndicator(),
};

// Records
(String name, int age) getUser() => ('Kim', 30);

// 使用例
final (name, age) = getUser();
print('$name is $age years old');

// Recordsの名前付きフィールド
({String name, int age}) getUserNamed() => (name: 'Kim', age: 30);

Flutterの核心パターン

// StatelessWidget + 状態管理
class UserListScreen extends StatelessWidget {
  const UserListScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Users')),
      body: Consumer<UserNotifier>(
        builder: (context, notifier, child) {
          return switch (notifier.state) {
            LoadingState() => const Center(
              child: CircularProgressIndicator(),
            ),
            ErrorState(:final message) => Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(message),
                  const SizedBox(height: 16),
                  ElevatedButton(
                    onPressed: notifier.retry,
                    child: const Text('Retry'),
                  ),
                ],
              ),
            ),
            SuccessState(:final data) => ListView.builder(
              itemCount: data.length,
              itemBuilder: (context, index) {
                final user = data[index];
                return UserCard(user: user);
              },
            ),
          };
        },
      ),
    );
  }
}

class UserCard extends StatelessWidget {
  final User user;
  const UserCard({super.key, required this.user});

  
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: ListTile(
        title: Text(user.name,
          style: Theme.of(context).textTheme.titleMedium),
        subtitle: Text(user.email),
        trailing: const Icon(Icons.chevron_right),
      ),
    );
  }
}

4. 機能比較(きのうひかく)

パフォーマンス

項目React NativeFlutter
レンダリングネイティブコンポーネント独自レンダリング(Impeller)
アニメーションReanimated(60fps)内蔵(120fps対応)
アプリ起動時間Hermesで改善AOTコンパイルで高速
メモリ使用量中程度中〜高
バンドルサイズより小さい(約7MB)より大きい(約15MB)
JSブリッジJSIで除去済み該当なし(ネイティブ)

開発者体験(DX)

項目React NativeFlutter
言語JavaScript/TypeScriptDart
Hot ReloadFast RefreshHot Reload/Hot Restart
IDEVS Code、WebStormVS Code、Android Studio
デバッグFlipper、Chrome DevToolsDevTools、Observatory
パッケージ生態系npm(膨大)pub.dev(成長中)
学習曲線Web開発者には低いDart学習が必要

ネイティブモジュール統合(とうごう)

項目React NativeFlutter
ネイティブAPITurboModules + JSIPlatform Channels / FFI
カメラexpo-cameracameraパッケージ
地図react-native-mapsgoogle_maps_flutter
プッシュ通知expo-notificationsfirebase_messaging
生体認証expo-local-authenticationlocal_auth

5. Expoディープダイブ

Expoの概要(がいよう)

Expoは React Native の開発を劇的(げきてき)に簡素化(かんそか)するプラットフォームです。2025年にはほとんどのReact NativeプロジェクトがExpoを使用(しよう)しています。

Expo Router v4

ファイルベースのルーティングでNext.jsに似(に)た体験を提供します。

app/
  _layout.tsx        <- ルートレイアウト
  index.tsx          <- ホーム画面 (/)
  (tabs)/
    _layout.tsx      <- タブナビゲーション
    home.tsx         <- /home
    profile.tsx      <- /profile
  users/
    [id].tsx         <- /users/123(動的ルート)
    index.tsx        <- /users
  settings/
    _layout.tsx      <- 設定レイアウト
    index.tsx        <- /settings
// app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="users/[id]" options={{ title: 'User Detail' }} />
    </Stack>
  );
}

// app/users/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';

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

  return (
    <View>
      <Text>User ID: {id}</Text>
    </View>
  );
}

// 型安全なナビゲーション
import { router } from 'expo-router';

function navigateToUser(userId: string) {
  router.push(`/users/${userId}`);
}

EAS(Expo Application Services)

# EAS Build - クラウドビルド
npx eas-cli build --platform ios --profile production
npx eas-cli build --platform android --profile production

# EAS Submit - ストア提出
npx eas-cli submit --platform ios
npx eas-cli submit --platform android

# EAS Update - OTAアップデート(コードのみ)
npx eas-cli update --branch production --message "Bug fix"

Expo SDKモジュール

// expo-camera
import { CameraView, useCameraPermissions } from 'expo-camera';

function CameraScreen() {
  const [permission, requestPermission] = useCameraPermissions();

  if (!permission?.granted) {
    return <Button title="Grant Permission" onPress={requestPermission} />;
  }

  return <CameraView style={{ flex: 1 }} facing="back" />;
}

// expo-notifications
import * as Notifications from 'expo-notifications';

async function schedulePushNotification() {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: "Reminder",
      body: 'Check your tasks!',
    },
    trigger: { seconds: 60 },
  });
}

6. パフォーマンスベンチマーク

アプリ起動時間(きどうじかん)(ms)

シナリオReact Native (Hermes)Flutter (AOT)Native
Cold Start (Android)約350ms約280ms約200ms
Cold Start (iOS)約400ms約300ms約250ms
Warm Start約150ms約120ms約100ms

スクロールパフォーマンス (FPS)

シナリオReact NativeFlutter
シンプルリスト(1000件)58-60 fps59-60 fps
画像リスト55-60 fps58-60 fps
複雑なカードレイアウト50-58 fps56-60 fps

アニメーションパフォーマンス

シナリオReact Native (Reanimated)Flutter
シンプルな遷移60 fps60 fps
複雑なジェスチャー55-60 fps58-60 fps
パーティクルシステム45-55 fps55-60 fps

メモリ使用量(しようりょう)(MB)

シナリオReact NativeFlutter
空のアプリ約40 MB約50 MB
リストビュー(100件)約65 MB約75 MB
画像ギャラリー約120 MB約130 MB

結論(けつろん): Flutterはアニメーション/レンダリングでわずかに優位、RNはメモリとバンドルサイズで有利。一般的なアプリでは差は微小です。


7. 状態管理(じょうたいかんり)

React Nativeの状態管理

Zustand(最も人気の選択肢)

import { create } from 'zustand';

interface UserStore {
  users: User[];
  isLoading: boolean;
  error: string | null;
  fetchUsers: () => Promise<void>;
}

const useUserStore = create<UserStore>((set) => ({
  users: [],
  isLoading: false,
  error: null,
  fetchUsers: async () => {
    set({ isLoading: true, error: null });
    try {
      const response = await fetch('https://api.example.com/users');
      const users = await response.json();
      set({ users, isLoading: false });
    } catch (error) {
      set({ error: (error as Error).message, isLoading: false });
    }
  },
}));

// コンポーネントでの使用
function UserList() {
  const { users, isLoading, fetchUsers } = useUserStore();

  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);

  if (isLoading) return <ActivityIndicator />;

  return (
    <FlatList
      data={users}
      renderItem={({ item }) => <UserCard user={item} />}
    />
  );
}

TanStack Query(サーバー状態)

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

function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
    staleTime: 5 * 60 * 1000, // 5分
  });
}

function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (newUser: CreateUserRequest) =>
      fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(newUser),
      }).then(r => r.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

Flutterの状態管理

Riverpod(最もおすすめ)

// Provider定義

class UserNotifier extends _$UserNotifier {
  
  FutureOr<List<User>> build() async {
    return await ref.read(userRepositoryProvider).getUsers();
  }

  Future<void> addUser(CreateUserRequest request) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await ref.read(userRepositoryProvider).createUser(request);
      return ref.read(userRepositoryProvider).getUsers();
    });
  }
}

// ウィジェットでの使用
class UserListScreen extends ConsumerWidget {
  const UserListScreen({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final usersAsync = ref.watch(userNotifierProvider);

    return usersAsync.when(
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (error, stack) => Center(child: Text('Error: $error')),
      data: (users) => ListView.builder(
        itemCount: users.length,
        itemBuilder: (context, index) => UserCard(user: users[index]),
      ),
    );
  }
}

Bloc(エンタープライズの選択)

// Event
sealed class UserEvent {}
class LoadUsers extends UserEvent {}
class CreateUser extends UserEvent {
  final CreateUserRequest request;
  CreateUser(this.request);
}

// State
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
  final List<User> users;
  UserLoaded(this.users);
}
class UserError extends UserState {
  final String message;
  UserError(this.message);
}

// Bloc
class UserBloc extends Bloc<UserEvent, UserState> {
  final UserRepository repository;

  UserBloc(this.repository) : super(UserInitial()) {
    on<LoadUsers>((event, emit) async {
      emit(UserLoading());
      try {
        final users = await repository.getUsers();
        emit(UserLoaded(users));
      } catch (e) {
        emit(UserError(e.toString()));
      }
    });
  }
}

8. ナビゲーション

Expo Router(React Native)

// 型安全なルーティング
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
  return (
    <Tabs screenOptions={{ tabBarActiveTintColor: '#2196F3' }}>
      <Tabs.Screen
        name="home"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

// ディープリンキングの自動サポート
// myapp://users/123 -> app/users/[id].tsx

go_router(Flutter)

final router = GoRouter(
  routes: [
    ShellRoute(
      builder: (context, state, child) => ScaffoldWithNavBar(child: child),
      routes: [
        GoRoute(
          path: '/',
          builder: (context, state) => const HomeScreen(),
        ),
        GoRoute(
          path: '/users',
          builder: (context, state) => const UserListScreen(),
          routes: [
            GoRoute(
              path: ':id',
              builder: (context, state) {
                final id = state.pathParameters['id']!;
                return UserDetailScreen(userId: id);
              },
            ),
          ],
        ),
        GoRoute(
          path: '/profile',
          builder: (context, state) => const ProfileScreen(),
        ),
      ],
    ),
  ],
);

// 使用例
context.go('/users/123');
context.push('/users/123');
context.pop();

9. テスティング

React Nativeのテスティング

Detox(E2E)

// e2e/users.test.ts
describe('User Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  it('should show user list', async () => {
    await expect(element(by.id('user-list'))).toBeVisible();
  });

  it('should navigate to user detail', async () => {
    await element(by.id('user-card-1')).tap();
    await expect(element(by.text('User Detail'))).toBeVisible();
  });

  it('should pull to refresh', async () => {
    await element(by.id('user-list')).swipe('down');
    await waitFor(element(by.id('loading-indicator')))
      .not.toBeVisible()
      .withTimeout(5000);
  });
});

Maestro(シンプルなE2E)

# .maestro/user-flow.yaml
appId: com.example.myapp
---
- launchApp
- assertVisible: "Users"
- tapOn: "Kim"
- assertVisible: "User Detail"
- back
- assertVisible: "Users"

Flutterのテスティング

Widgetテスト

void main() {
  testWidgets('UserCardはユーザー情報を表示する', (tester) async {
    final user = User(id: '1', name: 'Kim', email: 'kim@test.com');

    await tester.pumpWidget(
      MaterialApp(home: UserCard(user: user)),
    );

    expect(find.text('Kim'), findsOneWidget);
    expect(find.text('kim@test.com'), findsOneWidget);
  });

  testWidgets('UserListScreenはローディングインジケーターを表示する', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(home: UserListScreen()),
    );

    expect(find.byType(CircularProgressIndicator), findsOneWidget);
  });
}

Integrationテスト

// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('完全なユーザーフローテスト', (tester) async {
    await tester.pumpWidget(const MyApp());
    await tester.pumpAndSettle();

    // ユーザーリストの確認
    expect(find.text('Users'), findsOneWidget);

    // ユーザーカードをタップ
    await tester.tap(find.text('Kim'));
    await tester.pumpAndSettle();

    // 詳細画面の確認
    expect(find.text('User Detail'), findsOneWidget);
  });
}

Patrol(高度なE2E)

void main() {
  patrolTest('ネイティブインタラクション付きユーザーフロー', ($) async {
    await $.pumpWidgetAndSettle(const MyApp());

    // 権限ダイアログの処理
    await $.native.grantPermissionWhenInUse();

    // FlutterウィジェットとネイティブUI両方をテスト
    await $(#userList).scrollTo(find.text('Kim'));
    await $(find.text('Kim')).tap();
  });
}

10. 採用市場(さいようしじょう)と給与(きゅうよ)

2025年の市場概況

指標React NativeFlutter
LinkedIn求人(グローバル)約35,000+約25,000+
平均年収(米国)約13万〜17万ドル約12万〜16万ドル
フリーランス時給(米国)約80〜150ドル約70〜130ドル

どちらを先(さき)に学(まな)ぶべきか

React Nativeを先に学ぶべき場合:

  • すでにJavaScript/TypeScriptを知っている
  • Web開発経験がある
  • 迅速な就職が目標
  • 既存のReact Webプロジェクトとコード共有したい

Flutterを先に学ぶべき場合:

  • プログラミングを初めて始める
  • カスタムUI/アニメーションが重要なプロジェクト
  • デスクトップ/Webまで一つのコードで対応したい
  • Googleエコシステムに興味がある

11. 意思決定(いしけってい)フレームワーク

意思決定フローチャート

1. チームはJavaScript/TypeScriptに慣(な)れていますか?

  • はい → React Nativeを優先検討
  • いいえ → 評価を続行

2. 複雑なカスタムUI/アニメーションが核心ですか?

  • はい → Flutterを優先検討
  • いいえ → 評価を続行

3. Webアプリとコードを共有する必要がありますか?

  • はい → React Native(Reactコード共有)
  • いいえ → 評価を続行

4. デスクトップサポートが必要ですか?

  • はい → Flutter(より成熟したデスクトップサポート)
  • いいえ → プロジェクト要件に応じて選択

プロジェクトタイプ別推薦(すいせん)

プロジェクトタイプ推薦理由
EコマースアプリReact NativeWebとコード共有、Shopify連携
ソーシャルメディアFlutterカスタムUI、アニメーション
フィンテックアプリReact Native大きなJavaScriptエコシステム
ゲームFlutterカスタムレンダリングの強み
企業向けアプリReact Native採用の容易さ
IoTダッシュボードFlutterデスクトップ + モバイル統合
MVP/スタートアップExpo(RN)最速の開発速度
メディア/ストリーミングFlutterパフォーマンス最適化

12. インタビュー質問(しつもん)とクイズ

インタビュー質問10選(せん)

Q1: React Native New Architectureの核心コンポーネントを説明してください。

New ArchitectureはFabric(新レンダリングシステム)、TurboModules(ネイティブモジュールの遅延ロード)、JSI(JavaScript Interface)で構成されます。JSIは既存のブリッジを置き換え、JavaScriptとネイティブコード間の直接同期通信を可能にします。Codegenは型安全なネイティブインターフェースを自動生成します。

Q2: FlutterのImpellerがSkiaを置き換えた理由は?

Skiaベースのレンダリングはシェーダーコンパイルをランタイムで実行するため、最初のフレームでjank(カクつき)が発生していました。Impellerはシェーダーをビルド時に事前コンパイルしてこの問題を解決します。Metal(iOS)とVulkan(Android)を直接使用して予測可能なパフォーマンスを提供します。

Q3: React NativeとFlutterのレンダリング方式の違いは?

React NativeはプラットフォームのネイティブUIコンポーネント(UIKit/Android Views)を使用します。そのためプラットフォームのルック&フィールを自然に反映します。Flutterは独自のレンダリングエンジン(Impeller)で全てのピクセルを直接描画します。完全なUIカスタマイズが可能ですが、ネイティブの外観再現には追加作業が必要です。

Q4: Expoの利点と制約を説明してください。

利点:EAS BuildでCI/CDの簡素化、OTAアップデート(EAS Update)、ファイルベースルーティング(Expo Router)、豊富なSDKモジュール、Prebuildによるネイティブコード管理の自動化。制約:一部のネイティブモジュール未対応時にconfig pluginまたはejectが必要、ビルド時間がローカルより長くなる場合あり。

Q5: FlutterでRiverpodがProviderより好まれる理由は?

Riverpodはコンパイル時の安全性(ランタイムエラー削減)、BuildContext非依存、プロバイダー間の依存関係の明示的管理、テスト容易性(オーバーライドサポート)、autodisposeによる自動メモリ管理を提供します。Providerの制約(ランタイムエラー、柔軟性不足)を解決した進化版です。

Q6: React Nativeでのパフォーマンス最適化方法を説明してください。

FlatList使用(仮想化スクロール)、React.memo/useMemo/useCallbackで不要な再レンダリング防止、ReanimatedでUIスレッドアニメーション、Hermesエンジン有効化、画像最適化(キャッシング、リサイズ)、バンドル分析で不要な依存関係除去、New Architecture(JSI)の有効化。

Q7: Dart 3のパターンマッチングとRecordsがFlutterに与えた影響は?

パターンマッチングをsealed classとswitch式と組み合わせることで、型安全な状態処理が可能になりました。Recordsは複数の値を返す関数を簡潔にします。この組み合わせによりFlutterの状態管理コードがより安全で読みやすくなりました。

Q8: React NativeとFlutterのどちらかを選ぶ場合の判断基準は?

チームの技術スタック(JS経験ならRN)、UI要件(カスタムUIならFlutter)、コード共有戦略(Web共有ならRN)、採用市場(JS開発者プールが広い)、パフォーマンス要件(グラフィックス集約的ならFlutter)、既存インフラ(React WebがあればRN)。ほとんどの一般アプリでは大きな差がないため、チームの能力が最も重要です。

Q9: クロスプラットフォームアプリでのネイティブ機能アクセス方法を比較してください。

React Native:TurboModules + JSIでネイティブコード(Java/Kotlin、ObjC/Swift)と直接通信。ExpoはConfig Pluginでネイティブ設定を管理。Flutter:Platform Channels(メソッド/イベントチャネル)で非同期通信、またはFFIでCライブラリを直接呼び出し。両フレームワークともほとんどのネイティブAPIをカバーします。

Q10: クロスプラットフォームのテスティング戦略を説明してください。

ユニットテスト(ビジネスロジック)、ウィジェット/コンポーネントテスト(UI)、統合テスト(ユーザーシナリオ)、E2Eテスト(実デバイス)のテストピラミッドに従います。RNではJest + Testing Library + Detox/Maestro、FlutterではTest + Widget Test + integration_test + Patrolを使用します。

クイズ5選

Q1: React Native New ArchitectureにおけるJSIの役割は?

正解(せいかい):

JSI(JavaScript Interface)はJavaScriptとネイティブコード間の直接的な同期通信を可能にするインターフェースです。既存の非同期JSONシリアライゼーションブリッジを置き換えます。C++で記述されJavaScriptエンジンに独立しており、JavaScriptからC++オブジェクトを直接参照できます。これによりパフォーマンスが大幅に向上し、TurboModulesとFabricの基盤技術となっています。

Q2: FlutterでStatelessWidgetとStatefulWidgetの選択基準は?

正解:

StatelessWidgetは内部状態のないウィジェットに使用します。親から受け取ったデータのみ表示し、リビルド時に常に同じ結果を返します。StatefulWidgetは内部で変更可能な状態が必要な場合に使用します。しかし2025年にはRiverpodやBlocのような状態管理ライブラリの使用が一般的なので、ほとんどのウィジェットをStatelessWidget + 状態管理ライブラリの組み合わせで作成します。

Q3: Expo Routerのファイルベースルーティングと従来のReact Navigationの違いは?

正解:

React Navigationは明示的にナビゲーターとスクリーンをコードで定義します。型定義が別途必要でネストが複雑になる場合があります。Expo RouterはNext.jsのようにファイルシステム構造がそのままルートになります。自動ディープリンキング、型推論、レイアウトシステムを提供し、WebとモバイルでURLの同じ構造を使用できます。

Q4: React NativeのHermesエンジンがV8/JavaScriptCoreより優れている理由は?

正解:

Hermesはモバイル環境に最適化されたJavaScriptエンジンです。JavaScriptをバイトコードに事前コンパイル(AOT)し、アプリ起動時のパース時間を除去します。ガベージコレクションがモバイルに最適化され、メモリ使用量が少なくなります。アプリバンドルサイズも小さくなります。V8やJavaScriptCoreは汎用エンジンですが、HermesはモバイルReact Nativeに特化しています。

Q5: FlutterとReact NativeでそれぞれHot Reloadが動作する原理の違いは?

正解:

FlutterのHot ReloadはDart VMに変更されたソースコードを注入しウィジェットツリーをリビルドします。状態が保存されUIの変更を即座に確認できます。React NativeのFast RefreshはReactコンポーネントをホットスワップし、状態を維持しながらUIを更新します。両方ともアプリの再起動なしに変更を反映しますが、FlutterはVMレベルで、RNはバンドラー(Metro)レベルで動作します。


参考資料(さんこうしりょう)

  1. React Native公式ドキュメント - フレームワークガイド
  2. Flutter公式ドキュメント - フレームワークガイド
  3. Expoドキュメント - Expoプラットフォームガイド
  4. React Native New Architecture - 新アーキテクチャガイド
  5. Flutter Impeller - Impellerレンダリングエンジン
  6. Dart公式ドキュメント - Dart言語ガイド
  7. Expo Router - ファイルベースルーティング
  8. Riverpod - Flutter状態管理
  9. Zustand - React状態管理
  10. TanStack Query - サーバー状態管理
  11. Detox - React Native E2Eテスティング
  12. Patrol - Flutter高度なテスティング
  13. go_router - Flutterナビゲーション
  14. Reanimated - RNアニメーションライブラリ