- 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 |
핵심 선택 기준
- 팀 기술 스택: 웹 개발자 팀이라면 React Native, Dart 학습 의향이 있다면 Flutter
- UI 커스터마이징: 플랫폼 네이티브 룩을 원하면 RN, 완전한 커스텀 UI는 Flutter
- 성능 요구사항: 애니메이션 집약적이면 Flutter, 일반 앱은 둘 다 충분
- 기존 코드: 웹 코드 재사용은 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에서도 Stable
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');
// Named fields in 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 (성장 중) |
| 학습 곡선 | 웹 개발자에게 낮음 | 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 Test
void main() {
testWidgets('UserCard displays user info', (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 shows loading indicator', (tester) async {
await tester.pumpWidget(
const MaterialApp(home: UserListScreen()),
);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
}
Integration Test
// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('full user flow test', (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('User flow with native interactions', ($) 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+ |
| 한국 공고 | 약 800+ | 약 500+ |
| 평균 연봉 (미국) | 약 13만~17만 달러 | 약 12만~16만 달러 |
| 평균 연봉 (한국) | 약 5,500만~8,500만 원 | 약 5,000만~8,000만 원 |
| 프리랜서 시급 (미국) | 약 80~150 달러 | 약 70~130 달러 |
어떤 것을 먼저 배울까
React Native을 먼저 배워야 하는 경우:
- 이미 JavaScript/TypeScript를 알고 있음
- 웹 개발 경험이 있음
- 빠른 취업이 목표
- 기존 React 웹 프로젝트와 코드 공유하고 싶음
Flutter를 먼저 배워야 하는 경우:
- 프로그래밍을 처음 시작
- 커스텀 UI/애니메이션이 중요한 프로젝트
- 데스크톱/웹까지 하나의 코드로 지원하고 싶음
- Google 생태계에 관심
11. 프로젝트별 선택 프레임워크
의사 결정 플로우차트
1. 팀이 JavaScript/TypeScript에 익숙한가?
- Yes → React Native 우선 고려
- No → 계속 평가
2. 복잡한 커스텀 UI/애니메이션이 핵심인가?
- Yes → Flutter 우선 고려
- No → 계속 평가
3. 웹 코드를 공유해야 하는가?
- Yes → React Native (React 코드 공유)
- No → 계속 평가
4. 데스크톱 지원이 필요한가?
- Yes → Flutter (더 성숙한 데스크톱 지원)
- No → 프로젝트 요구사항에 따라 선택
프로젝트 유형별 추천
| 프로젝트 유형 | 추천 | 이유 |
|---|---|---|
| E-커머스 앱 | React Native | 웹과 코드 공유, 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), 코드 공유 전략(웹 공유면 RN), 채용 시장(JS 개발자 풀이 넓음), 성능 요구사항(그래픽 집약적이면 Flutter), 기존 인프라(React 웹이 있으면 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 vs StatefulWidget의 선택 기준은?
정답:
StatelessWidget은 내부 상태가 없는 위젯에 사용합니다. 부모로부터 받은 데이터만 표시하며 리빌드 시 항상 같은 결과를 반환합니다. StatefulWidget은 내부에서 변경 가능한 상태가 필요할 때 사용합니다. 그러나 2025년에는 Riverpod이나 Bloc 같은 상태 관리 라이브러리 사용이 일반적이므로, 대부분의 위젯을 StatelessWidget + 상태 관리 라이브러리 조합으로 작성합니다.
Q3: Expo Router의 파일 기반 라우팅과 기존 React Navigation의 차이점은?
정답:
React Navigation은 명시적으로 네비게이터와 스크린을 코드로 정의합니다. 타입 정의가 별도로 필요하고 중첩이 복잡해질 수 있습니다. Expo Router는 Next.js처럼 파일 시스템 구조가 곧 라우트입니다. 자동 딥 링킹, 타입 추론, 레이아웃 시스템을 제공하며, 웹과 모바일에서 동일한 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 - 임펠러 렌더링 엔진
- Dart 공식 문서 - Dart 언어 가이드
- Expo Router - 파일 기반 라우팅
- Riverpod - Flutter 상태 관리
- Zustand - React 상태 관리
- TanStack Query - 서버 상태 관리
- Detox - React Native E2E 테스팅
- Patrol - Flutter 고급 테스팅
- go_router - Flutter 네비게이션
- Reanimated - RN 애니메이션 라이브러리