# React Native 면접 질문 30선: 2026 완벽 가이드
> React Native 면접에서 가장 자주 등장하는 30가지 질문을 정리했습니다. 코드 예시가 포함된 상세 답변으로 모바일 개발자 채용에 대비할 수 있습니다.
- Published: 2026-02-01
- Updated: 2026-04-27
- Author: SharpSkill
- Tags: react native interview, mobile interview, react native questions, javascript, technical interview
- Reading time: 20 min
---
React Native 기술 면접에서는 크로스 플랫폼 모바일 개발 역량, iOS와 Android 고유 지식, 성능 패턴이 평가됩니다. 본 가이드는 가장 자주 등장하는 30가지 질문을 다루며, 효과적인 준비를 위한 상세 답변과 코드 예시를 함께 제공합니다.
> **준비 조언**
>
> 본 질문들은 기초부터 심화 개념까지 폭넓게 다룹니다. React Native 아키텍처를 이해하고 React 웹과의 차이를 파악하는 것이 면접 성공의 핵심입니다.
## React Native 기초
### 1. React와 React Native의 차이는 무엇입니까?
React는 웹 인터페이스를 구축하는 라이브러리이며, React Native는 iOS와 Android용 네이티브 모바일 앱 개발을 가능하게 합니다.
근본적인 차이는 렌더링 방식에 있습니다. React는 Virtual DOM을 사용해 HTML 요소로 변환하지만, React Native는 브리지를 통해 각 플랫폼의 네이티브 컴포넌트와 통신합니다.
```jsx
// React (Web) - uses HTML elements
function WebComponent() {
return (
Web text
)
}
// React Native - uses native components
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
function NativeComponent() {
return (
Native textPress
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16
}
})
```
React Native 컴포넌트는 iOS에서 UIView로, Android에서 android.view로 변환되어 네이티브 수준의 성능을 제공합니다.
### 2. React Native의 아키텍처는 어떻게 동작합니까?
React Native는 JavaScript, Bridge(신 아키텍처에서는 JSI), Native의 3계층 구조를 사용합니다.
JavaScript 코드는 JS 엔진(Hermes 또는 JavaScriptCore)에서 실행됩니다. 네이티브 코드와의 통신은 구 아키텍처에서는 JSON 직렬화를 거치고, 신 아키텍처에서는 JSI(JavaScript Interface)를 통해 직접 이루어집니다.
```jsx
// Old architecture: asynchronous communication via Bridge
// The Bridge serializes messages as JSON between JS and Native
// New architecture (Fabric + TurboModules)
// JSI enables synchronous direct calls to native modules
// Example TurboModule usage
import { TurboModuleRegistry } from 'react-native'
// Synchronous access to native module
const DeviceInfo = TurboModuleRegistry.get('DeviceInfo')
const deviceName = DeviceInfo.getDeviceName() // Synchronous call
// With Fabric, rendering is smoother
// Components can be created synchronously
// Reducing jank during animations
```
신 아키텍처는 JSON 직렬화를 제거하고 동기 호출을 가능하게 하여 성능을 크게 향상시킵니다.
### 3. Metro 번들러는 무엇입니까?
Metro는 React Native에서 사용하는 JavaScript 번들러로, 소스 코드를 모바일 실행에 최적화된 번들로 변환합니다.
Metro는 모듈 해석, Babel을 통한 코드 변환, 개발 시점의 핫 리로딩을 처리합니다.
```javascript
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
// Custom configuration
config.resolver.assetExts.push('db') // Add extensions
config.resolver.sourceExts.push('cjs') // CommonJS support
// Transformer configuration
config.transformer.babelTransformerPath = require.resolve(
'react-native-svg-transformer'
)
// Production optimizations
config.transformer.minifierConfig = {
keep_classnames: true,
keep_fnames: true,
mangle: {
keep_classnames: true,
keep_fnames: true
}
}
module.exports = config
```
Metro는 Fast Refresh를 지원하여 애플리케이션 상태를 잃지 않고 즉시 변경 사항을 반영합니다.
### 4. StyleSheet.create와 그 이점을 설명해 주십시오
`StyleSheet.create`는 스타일을 검증하고 숫자 참조로 변환하여 최적화하므로 브리지 통신 비용을 줄여 줍니다.
```jsx
// ❌ Inline styles - recreated on every render
function BadExample() {
return (
Title
)
}
// ✅ StyleSheet.create - optimized and validated
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff'
},
title: {
fontSize: 18,
fontWeight: 'bold'
},
// Style composition
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 8
}
})
function GoodExample() {
return (
Title
{/* Style combination */}
Content
)
}
// StyleSheet.absoluteFillObject for absolute positioning
const overlayStyles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)'
}
})
```
### 5. 웹 Flexbox와 React Native의 차이는 무엇입니까?
React Native도 Flexbox를 사용하지만, 세로 방향의 모바일 화면에 맞춰 웹과 다른 기본값이 적용됩니다.
```jsx
// Key differences from web
const styles = StyleSheet.create({
container: {
// flexDirection: 'column' by default (vs 'row' on web)
// alignItems: 'stretch' by default
flex: 1
},
// React Native Flexbox
row: {
flexDirection: 'row', // Horizontal
justifyContent: 'space-between', // Main axis
alignItems: 'center', // Cross axis
flexWrap: 'wrap', // Line wrapping
gap: 8 // Supported since RN 0.71
},
// Flex grow/shrink
flexItem: {
flex: 1, // Equivalent to flex: 1 1 0
flexGrow: 1, // Grow to fill
flexShrink: 0, // Don't shrink
flexBasis: 100 // Base size
},
// Absolute positioning
absolute: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
})
// Practical example: card with image and content
function Card() {
return (
TitleDescription
)
}
const cardStyles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: '#fff',
borderRadius: 8,
overflow: 'hidden'
},
image: {
width: 100,
height: 100
},
content: {
flex: 1, // Takes remaining space
padding: 12,
justifyContent: 'center'
},
title: {
fontSize: 16,
fontWeight: '600'
},
description: {
fontSize: 14,
color: '#666'
}
})
```
## 내비게이션과 컴포넌트
### 6. React Navigation으로 내비게이션을 어떻게 구현합니까?
React Navigation은 React Native의 표준 내비게이션 라이브러리로, 모바일 패턴에 적합한 다양한 내비게이터 유형을 제공합니다.
```jsx
// Installing dependencies
// npm install @react-navigation/native @react-navigation/native-stack
// npm install react-native-screens react-native-safe-area-context
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
// TypeScript typing for navigation params
type RootStackParamList = {
Home: undefined
Profile: { userId: string }
Settings: { section?: string }
}
const Stack = createNativeStackNavigator()
const Tab = createBottomTabNavigator()
// Tab navigation
function TabNavigator() {
return (
({
tabBarIcon: ({ focused, color, size }) => {
// Dynamic icon based on tab
const iconName = route.name === 'Home' ? 'home' : 'settings'
return
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray'
})}
>
)
}
// Stack navigation
function App() {
return (
({
title: `Profile ${route.params.userId}`
})}
/>
)
}
```
### 7. FlatList로 성능 좋은 리스트를 어떻게 다룹니까?
`FlatList`는 자동 가상화를 통해 화면에 보이는 항목만 렌더링하는, 긴 리스트에 최적화된 컴포넌트입니다.
```jsx
import { FlatList, RefreshControl } from 'react-native'
function ProductList() {
const [products, setProducts] = useState([])
const [refreshing, setRefreshing] = useState(false)
const [loading, setLoading] = useState(false)
// Initial loading
const fetchProducts = async (page = 1) => {
const response = await api.getProducts(page)
return response.data
}
// Pull-to-refresh
const onRefresh = useCallback(async () => {
setRefreshing(true)
const data = await fetchProducts(1)
setProducts(data)
setRefreshing(false)
}, [])
// Infinite pagination
const loadMore = useCallback(async () => {
if (loading) return
setLoading(true)
const nextPage = Math.ceil(products.length / 20) + 1
const data = await fetchProducts(nextPage)
setProducts(prev => [...prev, ...data])
setLoading(false)
}, [products.length, loading])
// Item rendering
const renderItem = useCallback(({ item }) => (
), [])
// Key extraction
const keyExtractor = useCallback((item) => item.id.toString(), [])
// Item separator
const ItemSeparator = useCallback(() => (
), [])
return (
}
// Infinite pagination
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? : null}
// Empty list
ListEmptyComponent={}
/>
)
}
```
> **FlatList 최적화**
>
> `renderItem`은 반드시 `useCallback`으로 메모화하고 무거운 컴포넌트는 별도로 분리해야 합니다. `renderItem` 내부의 인라인 함수는 불필요한 재렌더링을 유발합니다.
### 8. TouchableOpacity, Pressable, TouchableHighlight의 차이는 무엇입니까?
세 컴포넌트 모두 터치 이벤트를 처리하지만 시각적 피드백 방식이 서로 다릅니다.
```jsx
import {
TouchableOpacity,
TouchableHighlight,
Pressable,
StyleSheet
} from 'react-native'
function InteractionExamples() {
return (
{/* TouchableOpacity: reduces opacity on touch */}
console.log('Pressed')}
style={styles.button}
>
TouchableOpacity
{/* TouchableHighlight: adds background color */}
console.log('Pressed')}
style={styles.button}
>
TouchableHighlight
{/* Pressable: modern API with more control */}
console.log('Pressed')}
onLongPress={() => console.log('Long press')}
delayLongPress={500}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
>
{({ pressed }) => (
{pressed ? 'Pressed!' : 'Pressable'}
)}
{/* Pressable with hitSlop to enlarge touch area */}
console.log('Pressed')}
style={styles.smallButton}
>
Small button
)
}
const styles = StyleSheet.create({
container: {
gap: 16,
padding: 20
},
button: {
backgroundColor: '#007AFF',
padding: 16,
borderRadius: 8,
alignItems: 'center'
},
buttonPressed: {
backgroundColor: '#0056b3',
transform: [{ scale: 0.98 }]
},
textPressed: {
color: '#fff'
},
smallButton: {
padding: 8,
backgroundColor: '#eee'
}
})
```
신규 프로젝트에서는 더 세밀한 제어와 일관된 API를 제공하는 `Pressable` 사용이 권장됩니다.
### 9. 부드러운 애니메이션을 어떻게 구현합니까?
React Native는 여러 애니메이션 API를 제공합니다. 내장 Animated와 더 높은 성능을 제공하는 Reanimated가 대표적입니다.
```jsx
import { Animated, Easing } from 'react-native'
import Reanimated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming
} from 'react-native-reanimated'
// Animation with Animated (native API)
function FadeInView({ children }) {
const fadeAnim = useRef(new Animated.Value(0)).current
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
easing: Easing.ease,
useNativeDriver: true // Performant on UI thread
}).start()
}, [])
return (
{children}
)
}
// Animation with Reanimated (recommended for complex animations)
function BouncyButton() {
const scale = useSharedValue(1)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }]
}))
const handlePressIn = () => {
scale.value = withSpring(0.95, {
damping: 10,
stiffness: 400
})
}
const handlePressOut = () => {
scale.value = withSpring(1, {
damping: 10,
stiffness: 400
})
}
return (
Press
)
}
// List animation with LayoutAnimation
import { LayoutAnimation, UIManager, Platform } from 'react-native'
// Enable on Android
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental?.(true)
}
function AnimatedList() {
const [items, setItems] = useState([])
const addItem = () => {
// Configure animation before state change
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring)
setItems(prev => [...prev, { id: Date.now() }])
}
const removeItem = (id) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setItems(prev => prev.filter(item => item.id !== id))
}
return (
{items.map(item => (
removeItem(item.id)}>
Item {item.id}
))}
)
}
```
## 상태 관리와 데이터
### 10. React Native에서 글로벌 상태를 어떻게 관리합니까?
React 웹과 동일한 선택지를 사용할 수 있습니다. Context API, Redux, Zustand, MobX가 대표적입니다.
```jsx
// Lightweight solution with Zustand
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'
// Store with persistence
const useAuthStore = create(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (email, password) => {
const response = await api.login(email, password)
set({
user: response.user,
token: response.token,
isAuthenticated: true
})
},
logout: () => {
set({ user: null, token: null, isAuthenticated: false })
},
updateProfile: (updates) => {
set(state => ({
user: { ...state.user, ...updates }
}))
}
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage)
}
)
)
// Usage in a component
function ProfileScreen() {
const { user, logout, updateProfile } = useAuthStore()
if (!user) return
return (
{user.name}{user.email}Logout
)
}
// Cart store
const useCartStore = create((set, get) => ({
items: [],
addItem: (product) => set(state => {
const existing = state.items.find(i => i.id === product.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === product.id
? { ...i, quantity: i.quantity + 1 }
: i
)
}
}
return { items: [...state.items, { ...product, quantity: 1 }] }
}),
removeItem: (id) => set(state => ({
items: state.items.filter(i => i.id !== id)
})),
getTotal: () => {
return get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
},
clearCart: () => set({ items: [] })
}))
```
### 11. 캐시 관리와 함께 API 호출은 어떻게 처리합니까?
서버 상태 관리에는 React Query(TanStack Query)가 권장됩니다.
```jsx
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'
// Client configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
refetchOnWindowFocus: false // Mobile doesn't have "window focus"
}
}
})
// Provider in App.tsx
function App() {
return (
)
}
// Custom hook for products
function useProducts(categoryId) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: async () => {
const response = await fetch(`/api/products?category=${categoryId}`)
if (!response.ok) throw new Error('Network error')
return response.json()
},
enabled: !!categoryId // Don't execute if no categoryId
})
}
// Mutation with cache invalidation
function useAddToCart() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (product) => {
const response = await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify(product)
})
return response.json()
},
onSuccess: () => {
// Invalidate cart cache to force refetch
queryClient.invalidateQueries({ queryKey: ['cart'] })
},
onError: (error) => {
Alert.alert('Error', error.message)
}
})
}
// Usage in a component
function ProductListScreen({ categoryId }) {
const { data: products, isLoading, error, refetch } = useProducts(categoryId)
const addToCart = useAddToCart()
if (isLoading) return
if (error) return
return (
(
addToCart.mutate(item)}
isAddingToCart={addToCart.isPending}
/>
)}
refreshing={isLoading}
onRefresh={refetch}
/>
)
}
```
### 12. 데이터를 로컬에 어떻게 저장합니까?
데이터 종류에 따라 다양한 선택지가 있습니다. 단순 데이터에는 AsyncStorage, 성능이 중요할 때는 MMKV, 구조화된 데이터에는 SQLite를 사용합니다.
```jsx
// AsyncStorage - simple but slow for large volumes
import AsyncStorage from '@react-native-async-storage/async-storage'
const storage = {
async get(key) {
const value = await AsyncStorage.getItem(key)
return value ? JSON.parse(value) : null
},
async set(key, value) {
await AsyncStorage.setItem(key, JSON.stringify(value))
},
async remove(key) {
await AsyncStorage.removeItem(key)
},
async clear() {
await AsyncStorage.clear()
}
}
// MMKV - very performant (recommended)
import { MMKV } from 'react-native-mmkv'
const mmkv = new MMKV()
const fastStorage = {
get(key) {
const value = mmkv.getString(key)
return value ? JSON.parse(value) : null
},
set(key, value) {
mmkv.set(key, JSON.stringify(value))
},
remove(key) {
mmkv.delete(key)
},
// Primitive type support
getNumber(key) {
return mmkv.getNumber(key)
},
setNumber(key, value) {
mmkv.set(key, value)
},
getBoolean(key) {
return mmkv.getBoolean(key)
}
}
// Hook to use MMKV with React
function useMMKVStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
const stored = fastStorage.get(key)
return stored ?? defaultValue
})
const setStoredValue = useCallback((newValue) => {
setValue(newValue)
fastStorage.set(key, newValue)
}, [key])
return [value, setStoredValue]
}
// Usage
function SettingsScreen() {
const [theme, setTheme] = useMMKVStorage('theme', 'light')
const [notifications, setNotifications] = useMMKVStorage('notifications', true)
return (
setTheme(v ? 'dark' : 'light')}
/>
)
}
```
> **민감 데이터 저장**
>
> 토큰이나 개인 정보 같은 민감 데이터는 `expo-secure-store` 또는 `react-native-keychain`을 사용해야 합니다. 두 라이브러리 모두 iOS Keychain과 Android Keystore로 데이터를 암호화합니다.
## 성능과 최적화
### 13. React Native 앱 성능을 어떻게 최적화합니까?
최적화는 렌더링, 메모리, 사용자 상호작용 등 여러 측면을 포함합니다.
```jsx
// 1. Avoid unnecessary re-renders with React.memo
const ProductCard = React.memo(function ProductCard({ product, onPress }) {
return (
onPress(product.id)}>
{product.name}{product.price}$
)
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.product.id === nextProps.product.id
})
// 2. Memoize callbacks
function ProductList({ products }) {
// ❌ New function on every render
// onPress={(id) => handlePress(id)}
// ✅ Stable function
const handlePress = useCallback((id) => {
navigation.navigate('Product', { id })
}, [navigation])
return (
(
)}
/>
)
}
// 3. Optimize images
import FastImage from 'react-native-fast-image'
function OptimizedImage({ uri }) {
return (
)
}
// 4. Use InteractionManager for heavy tasks
import { InteractionManager } from 'react-native'
function HeavyScreen() {
const [data, setData] = useState(null)
useEffect(() => {
// Wait for animations to complete
const task = InteractionManager.runAfterInteractions(() => {
const result = performHeavyComputation()
setData(result)
})
return () => task.cancel()
}, [])
return data ? :
}
// 5. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
function Navigator() {
return (
)
}
```
### 14. 성능 문제를 어떻게 디버깅합니까?
React Native는 병목 구간을 식별하기 위한 다양한 도구를 제공합니다.
```jsx
// 1. Flipper - main debugging tool
// Configuration in android/app/build.gradle and ios/Podfile
// See: https://fbflipper.com/
// 2. Console.time to measure operations
function fetchData() {
console.time('fetchData')
const data = await api.getData()
console.timeEnd('fetchData') // fetchData: 234ms
return data
}
// 3. Performance monitor (shake device → Show Perf Monitor)
// Shows JS and UI FPS
// 4. Hermes profiler for CPU
// Enable in metro.config.js
module.exports = {
transformer: {
hermesParser: true
}
}
// 5. Identify re-renders with why-did-you-render
// Installation: npm install @welldone-software/why-did-you-render
import React from 'react'
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React, {
trackAllPureComponents: true
})
}
// Mark a component to monitor
ProductCard.whyDidYouRender = true
// 6. Measure mount time
function useComponentTiming(componentName) {
const mountTime = useRef(Date.now())
useEffect(() => {
const duration = Date.now() - mountTime.current
console.log(`${componentName} mounted in ${duration}ms`)
return () => {
console.log(`${componentName} unmounted`)
}
}, [componentName])
}
// Usage
function MyComponent() {
useComponentTiming('MyComponent')
// ...
}
```
### 15. 오프라인 모드는 어떻게 처리합니까?
오프라인 처리에는 캐싱과 동기화 전략이 필요합니다.
```jsx
import NetInfo from '@react-native-community/netinfo'
// Hook to monitor connectivity
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true)
const [connectionType, setConnectionType] = useState(null)
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected)
setConnectionType(state.type)
})
return () => unsubscribe()
}, [])
return { isConnected, connectionType }
}
// Service with offline queue
class OfflineQueue {
constructor() {
this.queue = []
this.isProcessing = false
}
async add(action) {
this.queue.push({
id: Date.now(),
action,
timestamp: new Date().toISOString()
})
await this.persist()
}
async persist() {
await AsyncStorage.setItem('offline_queue', JSON.stringify(this.queue))
}
async load() {
const data = await AsyncStorage.getItem('offline_queue')
this.queue = data ? JSON.parse(data) : []
}
async process() {
if (this.isProcessing || this.queue.length === 0) return
this.isProcessing = true
const { isConnected } = await NetInfo.fetch()
if (!isConnected) {
this.isProcessing = false
return
}
while (this.queue.length > 0) {
const item = this.queue[0]
try {
await this.executeAction(item.action)
this.queue.shift()
await this.persist()
} catch (error) {
console.error('Failed to process action:', error)
break
}
}
this.isProcessing = false
}
async executeAction(action) {
switch (action.type) {
case 'CREATE_ORDER':
return api.createOrder(action.payload)
case 'UPDATE_PROFILE':
return api.updateProfile(action.payload)
default:
throw new Error(`Unknown action: ${action.type}`)
}
}
}
const offlineQueue = new OfflineQueue()
// Network status banner component
function NetworkBanner() {
const { isConnected } = useNetworkStatus()
if (isConnected) return null
return (
Offline mode - Changes will be synchronized
)
}
```
## 모바일 고유 주제
### 16. iOS와 Android에서 권한을 어떻게 관리합니까?
권한은 플랫폼마다 처리 방식이 다릅니다. `react-native-permissions`와 같은 라이브러리를 사용하면 API를 통일할 수 있습니다.
```jsx
import { Platform, Alert, Linking } from 'react-native'
import {
check,
request,
PERMISSIONS,
RESULTS,
openSettings
} from 'react-native-permissions'
// Permission configuration per platform
const PERMISSION_TYPES = {
camera: Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA
}),
photos: Platform.select({
ios: PERMISSIONS.IOS.PHOTO_LIBRARY,
android: PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
}),
location: Platform.select({
ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
})
}
// Hook to manage permissions
function usePermission(type) {
const [status, setStatus] = useState(RESULTS.UNAVAILABLE)
const permission = PERMISSION_TYPES[type]
useEffect(() => {
check(permission).then(setStatus)
}, [permission])
const requestPermission = useCallback(async () => {
const result = await request(permission)
setStatus(result)
if (result === RESULTS.BLOCKED) {
Alert.alert(
'Permission required',
`This feature requires ${type} access. Do you want to open settings?`,
[
{ text: 'No', style: 'cancel' },
{ text: 'Open', onPress: openSettings }
]
)
}
return result
}, [permission, type])
return {
status,
isGranted: status === RESULTS.GRANTED,
isDenied: status === RESULTS.DENIED,
isBlocked: status === RESULTS.BLOCKED,
requestPermission
}
}
// Usage
function CameraButton() {
const { isGranted, requestPermission } = usePermission('camera')
const handlePress = async () => {
if (!isGranted) {
const result = await requestPermission()
if (result !== RESULTS.GRANTED) return
}
// Open camera
navigation.navigate('Camera')
}
return (
Take a photo
)
}
```
### 17. 푸시 알림은 어떻게 구현합니까?
푸시 알림 구현에는 네이티브 설정과 발송용 백엔드가 필요합니다.
```jsx
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance } from '@notifee/react-native'
// Notification service
class NotificationService {
async initialize() {
// Request permission (iOS)
const authStatus = await messaging().requestPermission()
const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED
if (enabled) {
// Get FCM token
const token = await messaging().getToken()
await this.registerToken(token)
// Listen for token changes
messaging().onTokenRefresh(this.registerToken)
// Create Android channel
await notifee.createChannel({
id: 'default',
name: 'Notifications',
importance: AndroidImportance.HIGH
})
}
return enabled
}
async registerToken(token) {
// Send token to backend
await api.registerPushToken(token)
}
// Setup handlers
setupHandlers() {
// Notification received in foreground
messaging().onMessage(async (remoteMessage) => {
await this.displayNotification(remoteMessage)
})
// Notification tapped (app in background)
messaging().onNotificationOpenedApp((remoteMessage) => {
this.handleNotificationPress(remoteMessage)
})
// App opened from notification (app closed)
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
this.handleNotificationPress(remoteMessage)
}
})
}
async displayNotification(remoteMessage) {
const { title, body } = remoteMessage.notification
await notifee.displayNotification({
title,
body,
android: {
channelId: 'default',
pressAction: { id: 'default' }
},
data: remoteMessage.data
})
}
handleNotificationPress(remoteMessage) {
const { type, id } = remoteMessage.data
switch (type) {
case 'order':
navigation.navigate('OrderDetail', { orderId: id })
break
case 'message':
navigation.navigate('Chat', { conversationId: id })
break
}
}
}
// Usage in App.tsx
function App() {
useEffect(() => {
const notificationService = new NotificationService()
notificationService.initialize()
notificationService.setupHandlers()
}, [])
return
}
```
### 18. 딥 링크는 어떻게 처리합니까?
딥 링크를 사용하면 외부 URL을 통해 앱의 특정 화면을 곧바로 열 수 있습니다.
```jsx
import { Linking } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
// Deep link configuration
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: 'home',
Product: {
path: 'product/:id',
parse: {
id: (id) => parseInt(id, 10)
}
},
Profile: 'profile/:userId?',
Settings: {
path: 'settings',
screens: {
Notifications: 'notifications',
Privacy: 'privacy'
}
}
}
},
// Custom function to get initial URL
async getInitialURL() {
// Check if app was opened via deep link
const url = await Linking.getInitialURL()
if (url) return url
// Check notifications
const message = await messaging().getInitialNotification()
if (message?.data?.link) return message.data.link
return null
},
// Subscribe to incoming links
subscribe(listener) {
// Standard deep links
const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
listener(url)
})
// Links from notifications
const unsubscribeNotification = messaging().onNotificationOpenedApp(
(message) => {
const link = message.data?.link
if (link) listener(link)
}
)
return () => {
linkingSubscription.remove()
unsubscribeNotification()
}
}
}
// Usage
function App() {
return (
}>
)
}
// Testing deep links
// myapp://product/123
// https://myapp.com/profile/user456
```
## 네이티브 코드와 모듈
### 19. 네이티브 코드는 언제 어떻게 작성합니까?
JavaScript에서 접근할 수 없는 기능이 필요하거나 핵심 성능을 끌어올려야 할 때 네이티브 코드가 필요합니다.
```jsx
// iOS - Native module in Swift
// ios/MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: NSObject {
@objc
func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let info: [String: Any] = [
"model": UIDevice.current.model,
"systemVersion": UIDevice.current.systemVersion,
"name": UIDevice.current.name
]
resolve(info)
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}
// ios/MyModule.m (Bridge)
#import
@interface RCT_EXTERN_MODULE(MyModule, NSObject)
RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
// Android - Native module in Kotlin
// android/app/src/main/java/com/myapp/MyModule.kt
package com.myapp
import com.facebook.react.bridge.*
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "MyModule"
@ReactMethod
fun getDeviceInfo(promise: Promise) {
val info = Arguments.createMap().apply {
putString("model", android.os.Build.MODEL)
putString("systemVersion", android.os.Build.VERSION.RELEASE)
putString("manufacturer", android.os.Build.MANUFACTURER)
}
promise.resolve(info)
}
}
// JavaScript - Using native module
import { NativeModules } from 'react-native'
const { MyModule } = NativeModules
async function getDeviceInfo() {
try {
const info = await MyModule.getDeviceInfo()
console.log('Device info:', info)
return info
} catch (error) {
console.error('Error getting device info:', error)
throw error
}
}
```
### 20. Expo는 무엇이며 언제 사용해야 합니까?
Expo는 네이티브 설정을 대신 관리하여 React Native 개발을 간소화하는 프레임워크입니다.
```jsx
// Expo benefits
// - No need for Android Studio or Xcode to start
// - OTA (over-the-air) updates without app stores
// - Rich SDK with preconfigured modules
// - EAS Build for cloud builds
// Creating an Expo project
// npx create-expo-app@latest MyApp
// Using Expo modules
import * as ImagePicker from 'expo-image-picker'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'
async function pickImage() {
// Request permission
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
alert('Permission denied')
return
}
// Open image picker
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8
})
if (!result.canceled) {
return result.assets[0].uri
}
}
// Build configuration
// app.json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.company.myapp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.company.myapp"
},
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Allow camera access"
}
]
]
}
}
// When NOT to use Expo
// - Need for complex custom native modules
// - Integration with specific native SDKs
// - Total control over native configuration
// - Very lightweight application (Expo adds weight)
```
### 21. Hot Reloading은 어떻게 동작합니까?
Hot Reloading(Fast Refresh)은 애플리케이션 상태를 유지한 채 즉시 변경 사항을 반영합니다.
```jsx
// Fast Refresh preserves hooks state
function Counter() {
const [count, setCount] = useState(0)
// Modify this text and save
// The count state will be preserved
return (
Counter: {count}
)
}
// ⚠️ Cases where Fast Refresh does full reload
// 1. Syntax error
// 2. Modifying a class component
// 3. File with mixed non-component exports
// ❌ This file will do full reload because of mixed exports
export const API_URL = 'https://api.example.com'
export function MyComponent() { /* ... */ }
// ✅ Separate into distinct files
// constants.js
export const API_URL = 'https://api.example.com'
// MyComponent.js
export function MyComponent() { /* ... */ }
// Force remount if needed
// Add this comment at top of file:
// @refresh reset
// Metro configuration for Fast Refresh
// metro.config.js
module.exports = {
transformer: {
experimentalImportSupport: false,
inlineRequires: true
}
}
```
## 테스트와 품질
### 22. React Native 애플리케이션을 어떻게 테스트합니까?
React Native 테스트는 Jest와 함께 렌더링과 상호작용을 위한 전용 라이브러리를 사용합니다.
```jsx
// Jest configuration
// jest.config.js
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation)/)'
],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|webp|svg)$': '/__mocks__/fileMock.js'
}
}
// Component test with React Native Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native'
import { ProductCard } from './ProductCard'
describe('ProductCard', () => {
const mockProduct = {
id: '1',
name: 'iPhone 15',
price: 999,
image: 'https://example.com/iphone.jpg'
}
it('renders product information', () => {
render()
expect(screen.getByText('iPhone 15')).toBeOnTheScreen()
expect(screen.getByText('$999')).toBeOnTheScreen()
})
it('calls onPress when tapped', () => {
const onPress = jest.fn()
render()
fireEvent.press(screen.getByTestId('product-card'))
expect(onPress).toHaveBeenCalledWith('1')
})
it('shows loading state when adding to cart', async () => {
const onAddToCart = jest.fn(() => new Promise(r => setTimeout(r, 100)))
render()
fireEvent.press(screen.getByText('Add to cart'))
expect(screen.getByTestId('loading-indicator')).toBeOnTheScreen()
await waitFor(() => {
expect(screen.queryByTestId('loading-indicator')).not.toBeOnTheScreen()
})
})
})
// Navigation test
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
function renderWithNavigation(component, { initialRouteName = 'Test' } = {}) {
return render(
)
}
// Custom hook test
import { renderHook, act } from '@testing-library/react-native'
import { useCounter } from './useCounter'
test('increments counter', () => {
const { result } = renderHook(() => useCounter(0))
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
```
### 23. Detox로 E2E 테스트를 어떻게 구현합니까?
Detox는 실제 시뮬레이터/에뮬레이터에서 애플리케이션 전체를 테스트할 수 있게 해 줍니다.
```jsx
// Installation
// npm install detox --save-dev
// detox init -r jest
// .detoxrc.js
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'e2e/jest.config.js'
},
jest: {
setupTimeout: 120000
}
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/MyApp.app',
build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug'
}
},
devices: {
simulator: {
type: 'ios.simulator',
device: { type: 'iPhone 15' }
},
emulator: {
type: 'android.emulator',
device: { avdName: 'Pixel_5_API_34' }
}
}
}
// e2e/login.test.js
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp()
})
beforeEach(async () => {
await device.reloadReactNative()
})
it('should login successfully with valid credentials', async () => {
// Fill the form
await element(by.id('email-input')).typeText('test@example.com')
await element(by.id('password-input')).typeText('password123')
// Submit
await element(by.id('login-button')).tap()
// Verify navigation to dashboard
await waitFor(element(by.text('Welcome')))
.toBeVisible()
.withTimeout(5000)
})
it('should show error with invalid credentials', async () => {
await element(by.id('email-input')).typeText('wrong@example.com')
await element(by.id('password-input')).typeText('wrongpassword')
await element(by.id('login-button')).tap()
await expect(element(by.text('Invalid credentials'))).toBeVisible()
})
it('should navigate to forgot password', async () => {
await element(by.id('forgot-password-link')).tap()
await expect(element(by.text('Reset Password'))).toBeVisible()
})
})
// Detox commands
// detox build --configuration ios.debug
// detox test --configuration ios.debug
```
## 배포와 프로덕션
### 24. 환경(dev, staging, prod)을 어떻게 관리합니까?
환경 관리에는 환경별로 구분된 설정 변수가 필요합니다.
```jsx
// Option 1: react-native-config
// .env.development
API_URL=https://dev-api.myapp.com
ANALYTICS_KEY=dev_key
// .env.staging
API_URL=https://staging-api.myapp.com
ANALYTICS_KEY=staging_key
// .env.production
API_URL=https://api.myapp.com
ANALYTICS_KEY=prod_key
// Usage
import Config from 'react-native-config'
const api = {
baseUrl: Config.API_URL,
analyticsKey: Config.ANALYTICS_KEY
}
// Option 2: JavaScript configuration file
// config/index.js
const ENV = {
development: {
apiUrl: 'https://dev-api.myapp.com',
analyticsEnabled: false,
logLevel: 'debug'
},
staging: {
apiUrl: 'https://staging-api.myapp.com',
analyticsEnabled: true,
logLevel: 'info'
},
production: {
apiUrl: 'https://api.myapp.com',
analyticsEnabled: true,
logLevel: 'error'
}
}
const getEnv = () => {
if (__DEV__) return 'development'
// Logic to determine staging vs prod
return 'production'
}
export const config = ENV[getEnv()]
// Option 3: Expo with app.config.js
// app.config.js
export default ({ config }) => {
const env = process.env.APP_ENV || 'development'
const envConfig = {
development: {
apiUrl: 'https://dev-api.myapp.com',
bundleId: 'com.myapp.dev'
},
production: {
apiUrl: 'https://api.myapp.com',
bundleId: 'com.myapp'
}
}
return {
...config,
extra: {
...envConfig[env],
env
},
ios: {
bundleIdentifier: envConfig[env].bundleId
},
android: {
package: envConfig[env].bundleId
}
}
}
```
### 25. 앱 스토어에는 어떻게 배포합니까?
배포는 빌드 설정, 메타데이터 작성, 제출 단계로 이루어집니다.
```bash
# Option 1: EAS Build (Expo)
# Installation
npm install -g eas-cli
# Configuration
eas build:configure
# eas.json
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "your@email.com",
"ascAppId": "1234567890"
},
"android": {
"serviceAccountKeyPath": "./google-services.json",
"track": "production"
}
}
}
}
# Production build
eas build --platform all --profile production
# Store submission
eas submit --platform all --profile production
# Option 2: Fastlane (React Native CLI)
# Gemfile
source "https://rubygems.org"
gem "fastlane"
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Deploy to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(scheme: "MyApp")
upload_to_testflight
end
desc "Deploy to App Store"
lane :release do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(scheme: "MyApp")
upload_to_app_store(
skip_screenshots: true,
skip_metadata: true
)
end
end
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Deploy to Play Store internal"
lane :beta do
gradle(task: "clean bundleRelease")
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end
end
```
### 26. OTA 업데이트는 어떻게 구현합니까?
Over-The-Air 업데이트를 사용하면 스토어를 거치지 않고 JavaScript 코드를 배포할 수 있습니다.
```jsx
// With Expo Updates
import * as Updates from 'expo-updates'
async function checkForUpdates() {
if (__DEV__) return // Not in development
try {
const update = await Updates.checkForUpdateAsync()
if (update.isAvailable) {
// Download update
await Updates.fetchUpdateAsync()
// Ask user to restart
Alert.alert(
'Update available',
'A new version is available. Restart now?',
[
{ text: 'Later', style: 'cancel' },
{
text: 'Restart',
onPress: () => Updates.reloadAsync()
}
]
)
}
} catch (error) {
console.error('Update check error:', error)
}
}
// Automatic check on startup
function App() {
useEffect(() => {
checkForUpdates()
}, [])
return
}
// eas.json configuration for update channels
{
"build": {
"production": {
"channel": "production"
},
"preview": {
"channel": "preview"
}
}
}
// Command to publish an update
// eas update --branch production --message "Bug fix"
// With CodePush (Microsoft)
import codePush from 'react-native-code-push'
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESTART
}
function App() {
return
}
export default codePush(codePushOptions)(App)
```
## 심화 질문
### 27. 앱의 시작 시간을 어떻게 최적화합니까?
시작 시간은 사용자 경험에 직결되는 중요한 요소입니다.
```jsx
// 1. Enable Hermes (optimized JS engine)
// android/app/build.gradle
project.ext.react = [
enableHermes: true
]
// ios/Podfile
:hermes_enabled => true
// 2. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
// 3. Defer non-critical initializations
import { InteractionManager } from 'react-native'
function App() {
useEffect(() => {
// Execute after first render
InteractionManager.runAfterInteractions(() => {
// Initialize analytics
Analytics.init()
// Prefetch data
prefetchCriticalData()
})
}, [])
return
}
// 4. Optimize splash screen
import * as SplashScreen from 'expo-splash-screen'
// Prevent automatic hiding
SplashScreen.preventAutoHideAsync()
function App() {
const [appIsReady, setAppIsReady] = useState(false)
useEffect(() => {
async function prepare() {
try {
// Load critical resources
await Font.loadAsync(customFonts)
await Image.prefetch(criticalImages)
// Restore authentication
await restoreAuth()
} catch (e) {
console.warn(e)
} finally {
setAppIsReady(true)
}
}
prepare()
}, [])
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// Hide splash screen
await SplashScreen.hideAsync()
}
}, [appIsReady])
if (!appIsReady) return null
return (
)
}
// 5. Inline requires to defer imports
// metro.config.js
module.exports = {
transformer: {
inlineRequires: true
}
}
// Manual usage
function loadHeavyModule() {
const HeavyModule = require('./HeavyModule').default
return HeavyModule
}
```
### 28. React Native 앱의 보안은 어떻게 다룹니까?
모바일 보안에는 여러 계층의 방어가 필요합니다.
```jsx
// 1. Secure storage for sensitive data
import * as SecureStore from 'expo-secure-store'
// or
import * as Keychain from 'react-native-keychain'
async function saveToken(token) {
await SecureStore.setItemAsync('auth_token', token)
}
async function getToken() {
return await SecureStore.getItemAsync('auth_token')
}
// 2. Certificate pinning for network calls
// android/app/src/main/res/xml/network_security_config.xml
/*
api.myapp.comAAAAAA...BBBBBB...
*/
// 3. Jailbreak/root detection
import JailMonkey from 'jail-monkey'
function SecurityCheck() {
useEffect(() => {
if (JailMonkey.isJailBroken()) {
Alert.alert(
'Insecure device',
'This application cannot run on a rooted/jailbroken device'
)
}
}, [])
}
// 4. Code obfuscation
// metro.config.js (for Hermes)
module.exports = {
transformer: {
minifierConfig: {
mangle: true,
output: {
ascii_only: true
}
}
}
}
// 5. Screenshot/recording protection
import { usePreventScreenCapture } from 'expo-screen-capture'
function SensitiveScreen() {
usePreventScreenCapture() // iOS only
return {/* Sensitive data */}
}
// 6. Session timeout
function useSessionTimeout(timeoutMs = 5 * 60 * 1000) {
const lastActivity = useRef(Date.now())
const { logout } = useAuth()
useEffect(() => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') {
const elapsed = Date.now() - lastActivity.current
if (elapsed > timeoutMs) {
logout()
}
} else {
lastActivity.current = Date.now()
}
})
return () => subscription.remove()
}, [timeoutMs, logout])
}
```
### 29. 접근성은 어떻게 구현합니까?
접근성은 모든 사용자가 앱을 사용할 수 있도록 보장하기 위해 필수입니다.
```jsx
import { AccessibilityInfo } from 'react-native'
// 1. Basic accessibility props
function AccessibleButton({ label, onPress, disabled }) {
return (
{label}
)
}
// 2. Group elements for screen readers
function ProductCard({ product }) {
return (
{product.name}${product.price}
)
}
// 3. Announce dynamic changes
function NotificationBadge({ count }) {
useEffect(() => {
if (count > 0) {
AccessibilityInfo.announceForAccessibility(
`${count} new notification${count > 1 ? 's' : ''}`
)
}
}, [count])
return (
{count}
)
}
// 4. Detect accessibility preferences
function useAccessibilityPreferences() {
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false)
const [isReduceMotionEnabled, setIsReduceMotionEnabled] = useState(false)
useEffect(() => {
AccessibilityInfo.isScreenReaderEnabled().then(setIsScreenReaderEnabled)
AccessibilityInfo.isReduceMotionEnabled().then(setIsReduceMotionEnabled)
const screenReaderListener = AccessibilityInfo.addEventListener(
'screenReaderChanged',
setIsScreenReaderEnabled
)
const reduceMotionListener = AccessibilityInfo.addEventListener(
'reduceMotionChanged',
setIsReduceMotionEnabled
)
return () => {
screenReaderListener.remove()
reduceMotionListener.remove()
}
}, [])
return { isScreenReaderEnabled, isReduceMotionEnabled }
}
// 5. Adapt animations based on preferences
function AnimatedComponent() {
const { isReduceMotionEnabled } = useAccessibilityPreferences()
const animation = useSharedValue(0)
useEffect(() => {
animation.value = withTiming(1, {
duration: isReduceMotionEnabled ? 0 : 300
})
}, [isReduceMotionEnabled])
return
}
```
### 30. 대규모 React Native 프로젝트는 어떻게 구성합니까?
명확한 아키텍처는 유지보수성과 확장성을 결정합니다.
```
// Recommended structure
src/
├── app/ # App configuration
│ ├── App.tsx
│ ├── Navigation.tsx
│ └── Providers.tsx
│
├── features/ # Feature modules
│ ├── auth/
│ │ ├── screens/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── components/
│ │ │ └── AuthForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ └── index.ts # Public export
│ │
│ ├── products/
│ │ ├── screens/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ │
│ └── cart/
│ └── ...
│
├── shared/ # Shared code
│ ├── components/
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Card.tsx
│ ├── hooks/
│ │ ├── useDebounce.ts
│ │ └── useNetworkStatus.ts
│ ├── utils/
│ │ ├── format.ts
│ │ └── validation.ts
│ └── types/
│ └── index.ts
│
├── services/ # Global services
│ ├── api/
│ │ ├── client.ts
│ │ └── interceptors.ts
│ ├── storage/
│ │ └── secureStorage.ts
│ └── analytics/
│ └── analytics.ts
│
├── store/ # Global state
│ ├── slices/
│ └── index.ts
│
└── theme/ # Design system
├── colors.ts
├── typography.ts
├── spacing.ts
└── index.ts
```
```jsx
// Example feature module organization
// features/products/index.ts
export { ProductListScreen } from './screens/ProductListScreen'
export { ProductDetailScreen } from './screens/ProductDetailScreen'
export { useProducts } from './hooks/useProducts'
export { ProductCard } from './components/ProductCard'
// features/products/hooks/useProducts.ts
import { useQuery } from '@tanstack/react-query'
import { productService } from '../services/productService'
export function useProducts(categoryId?: string) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: () => productService.getProducts(categoryId)
})
}
// features/products/services/productService.ts
import { apiClient } from '@/services/api/client'
import { Product } from '../types'
export const productService = {
async getProducts(categoryId?: string): Promise {
const params = categoryId ? { category: categoryId } : {}
const response = await apiClient.get('/products', { params })
return response.data
},
async getProduct(id: string): Promise {
const response = await apiClient.get(`/products/${id}`)
return response.data
}
}
// Import alias configuration
// babel.config.js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
[
'module-resolver',
{
alias: {
'@': './src',
'@features': './src/features',
'@shared': './src/shared',
'@services': './src/services'
}
}
]
]
}
```
## 결론
본 30가지 질문은 React Native 면접에서 요구되는 핵심 지식을 폭넓게 다룹니다. 반드시 숙지해야 할 핵심은 다음과 같습니다.
- **아키텍처**: 브리지, JSI, 신 아키텍처에 대한 이해
- **컴포넌트**: 내비게이션, 고성능 리스트, 애니메이션
- **상태와 데이터**: Zustand/Redux, React Query, 로컬 스토리지
- **성능**: FlatList 최적화, 메모화, 프로파일링
- **모바일 고유 주제**: 권한, 알림, 딥 링크
- **테스트**: Jest, Testing Library, Detox
- **배포**: EAS Build, 스토어 등록, OTA 업데이트
- **보안과 접근성**: 보안 저장소, WCAG 준수
React Native 면접 준비에는 React 웹 지식 외에 모바일 고유의 측면을 이해하는 작업이 필요합니다. 실제 프로젝트와 실기기 테스트를 병행하며 이 개념들을 견고히 다질 수 있습니다.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/ko/blog/react-native/react-native-interview-questions