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

- Name
- Youngju Kim
- @fjvbn20031
目次(もくじ)
1. 2025年(ねん)クロスプラットフォームモバイル開発(かいはつ)の展望(てんぼう)
市場(しじょう)の現状(げんじょう)
2025年、モバイル開発市場でクロスプラットフォームフレームワークのシェアは増加(ぞうか)し続(つづ)けています。ネイティブ開発と比(くら)べたコスト削減(さくげん)と迅速(じんそく)な市場投入が主要な動機です。
| フレームワーク | GitHub Stars | 週間ダウンロード/使用 | 主要企業 |
|---|---|---|---|
| React Native | 120k+ | npm 2M+/週 | Meta、Microsoft、Shopify |
| Flutter | 165k+ | 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年には事実上(じじつじょう)の標準(ひょうじゅん)となりました。
核心(かくしん)コンポーネント:
- Fabric: 新レンダリングシステム。同期レンダリングでUI応答性向上
- TurboModules: ネイティブモジュールの遅延ロード。アプリ起動時間短縮
- JSI(JavaScript Interface): JavaScriptとネイティブコード間の直接通信。ブリッジ除去
- 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 Native | Flutter |
|---|---|---|
| レンダリング | ネイティブコンポーネント | 独自レンダリング(Impeller) |
| アニメーション | Reanimated(60fps) | 内蔵(120fps対応) |
| アプリ起動時間 | Hermesで改善 | AOTコンパイルで高速 |
| メモリ使用量 | 中程度 | 中〜高 |
| バンドルサイズ | より小さい(約7MB) | より大きい(約15MB) |
| JSブリッジ | JSIで除去済み | 該当なし(ネイティブ) |
開発者体験(DX)
| 項目 | React Native | Flutter |
|---|---|---|
| 言語 | JavaScript/TypeScript | Dart |
| Hot Reload | Fast Refresh | Hot Reload/Hot Restart |
| IDE | VS Code、WebStorm | VS Code、Android Studio |
| デバッグ | Flipper、Chrome DevTools | DevTools、Observatory |
| パッケージ生態系 | npm(膨大) | pub.dev(成長中) |
| 学習曲線 | Web開発者には低い | Dart学習が必要 |
ネイティブモジュール統合(とうごう)
| 項目 | React Native | Flutter |
|---|---|---|
| ネイティブAPI | TurboModules + JSI | Platform Channels / FFI |
| カメラ | expo-camera | cameraパッケージ |
| 地図 | react-native-maps | google_maps_flutter |
| プッシュ通知 | expo-notifications | firebase_messaging |
| 生体認証 | expo-local-authentication | local_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 Native | Flutter |
|---|---|---|
| シンプルリスト(1000件) | 58-60 fps | 59-60 fps |
| 画像リスト | 55-60 fps | 58-60 fps |
| 複雑なカードレイアウト | 50-58 fps | 56-60 fps |
アニメーションパフォーマンス
| シナリオ | React Native (Reanimated) | Flutter |
|---|---|---|
| シンプルな遷移 | 60 fps | 60 fps |
| 複雑なジェスチャー | 55-60 fps | 58-60 fps |
| パーティクルシステム | 45-55 fps | 55-60 fps |
メモリ使用量(しようりょう)(MB)
| シナリオ | React Native | Flutter |
|---|---|---|
| 空のアプリ | 約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 Native | Flutter |
|---|---|---|
| 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 Native | Webとコード共有、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)レベルで動作します。
参考資料(さんこうしりょう)
- React Native公式ドキュメント - フレームワークガイド
- Flutter公式ドキュメント - フレームワークガイド
- Expoドキュメント - Expoプラットフォームガイド
- React Native New Architecture - 新アーキテクチャガイド
- Flutter Impeller - Impellerレンダリングエンジン
- Dart公式ドキュメント - Dart言語ガイド
- Expo Router - ファイルベースルーティング
- Riverpod - Flutter状態管理
- Zustand - React状態管理
- TanStack Query - サーバー状態管理
- Detox - React Native E2Eテスティング
- Patrol - Flutter高度なテスティング
- go_router - Flutterナビゲーション
- Reanimated - RNアニメーションライブラリ