React는 사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리로, Angular, Vue.js와 함께 주요 프론트엔드 프레임워크/라이브러리 중 하나입니다.
| 특징 | React | Angular | Vue.js |
|---|---|---|---|
| 타입 | 라이브러리 | 프레임워크 | 프레임워크 |
| 학습곡선 | 중간 | 높음 | 낮음 |
| 렌더링 | Virtual DOM | Real DOM | Virtual DOM |
| 데이터 바인딩 | 단방향 | 양방향 | 양방향/단방향 |
| TypeScript | 별도 설정 | 기본 지원 | 별도 설정 |
| 생태계 | 매우 풍부 | 풍부 | 보통 |
// 1. 컴포넌트 기반 아키텍처
function Header() {
return <h1>My App</h1>;
}
// 2. 재사용 가능한 컴포넌트
function Button({ text, onClick }) {
return <button onClick={onClick}>{text}</button>;
}
// 3. Virtual DOM으로 효율적인 업데이트
// React가 자동으로 최적화된 DOM 업데이트 수행
"React는 Virtual DOM을 사용하는 라이브러리입니다. Angular는 완전한 프레임워크로 양방향 데이터 바인딩을 제공하지만, React는 단방향 데이터 플로우로 예측 가능한 상태 관리를 제공합니다. Vue.js는 학습 곡선이 낮지만, React는 더 큰 생태계와 높은 유연성을 제공합니다."
Virtual DOM은 실제 DOM의 가상 표현으로, 메모리 상에서 동작하여 성능을 최적화합니다.
// 1. JSX 작성
function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
// 2. Babel이 JSX를 React.createElement로 변환
React.createElement(
'div',
null,
React.createElement('p', null, `Count: ${count}`),
React.createElement('button', { onClick: handleClick }, 'Increment')
);
// 이전 Virtual DOM Tree
{
type: 'div',
props: {
children: [
{ type: 'p', props: { children: 'Count: 0' } }
]
}
}
// 새로운 Virtual DOM Tree
{
type: 'div',
props: {
children: [
{ type: 'p', props: { children: 'Count: 1' } }
]
}
}
// Diff 알고리즘이 변경사항을 감지하고 최소한의 DOM 업데이트 수행
// React 18의 동시성 기능
import { startTransition } from 'react';
function App() {
const [count, setCount] = useState(0);
const [list, setList] = useState([]);
const handleClick = () => {
// 긴급한 업데이트
setCount(count + 1);
// 긴급하지 않은 업데이트 (중단 가능)
startTransition(() => {
setList(generateLargeList());
});
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Update</button>
<List items={list} />
</div>
);
}
"React는 Virtual DOM을 사용하여 효율적인 렌더링을 수행합니다. 상태가 변경되면 새로운 Virtual DOM 트리를 생성하고, Diff 알고리즘을 통해 이전 트리와 비교하여 변경된 부분만 실제 DOM에 반영합니다. React 18에서는 Fiber 아키텍처로 동시성 렌더링을 지원하여 사용자 인터랙션을 차단하지 않고 백그라운드에서 렌더링을 수행할 수 있습니다."
// Class Component 시절
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
// Hooks 이후 (useState 사용)
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
// 1. 상태 배치(Batching)
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const handleClick = () => {
setCount(count + 1); // 배치됨
setName('Updated'); // 배치됨
// React 18에서는 하나의 리렌더링으로 처리
};
}
// 2. 함수형 업데이트
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// 이전 값을 기반으로 업데이트 (안전함)
setCount(prevCount => prevCount + 1);
};
}
// 3. 지연 초기화 (Lazy Initial State)
function ExpensiveComponent() {
const [data, setData] = useState(() => {
// 컴포넌트 최초 렌더링 시에만 실행
return computeExpensiveValue();
});
}
"useState는 함수형 컴포넌트에서 상태를 관리하기 위한 Hook입니다. 클래스 컴포넌트의 this.state와 달리, 함수형 컴포넌트는 실행될 때마다 지역 변수가 초기화되므로 상태를 유지할 수 없습니다. useState는 React의 렌더링 사이클과 통합되어 상태 변경 시 자동으로 리렌더링을 트리거하고, 상태 배치 처리로 성능을 최적화합니다."
컴포넌트는 UI를 독립적이고 재사용 가능한 조각으로 나누어 각 조각을 개별적으로 처리할 수 있게 해주는 React의 핵심 개념입니다.
// 기본 함수형 컴포넌트
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// Hooks를 사용한 함수형 컴포넌트
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <div>Loading...</div>;
return (
<div className="user-profile">
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
// 단일 책임 원칙 적용
function UserDashboard({ userId }) {
return (
<div className="dashboard">
<UserProfile userId={userId} />
<UserPosts userId={userId} />
<UserComments userId={userId} />
</div>
);
}
// 컴포넌트 합성 패턴
function DataFetcher({ url, children }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return children({ data, loading });
}
"React 컴포넌트는 UI를 독립적이고 재사용 가능한 조각으로 나누는 기본 단위입니다. 함수형과 클래스형이 있으며, 현재는 Hooks를 사용한 함수형 컴포넌트가 권장됩니다. 컴포넌트는 Props로 데이터를 받고, State로 내부 상태를 관리하며, 단일 책임 원칙을 따라 설계해야 합니다."
Redux store는 JavaScript 힙 메모리에 저장되며, 브라우저의 메인 스레드에서 관리됩니다.
// Redux Store는 JavaScript 객체로 메모리에 저장
const store = {
// 현재 애플리케이션 상태 (메모리에 상주)
state: {
user: { id: 1, name: 'John' },
posts: [...],
ui: { loading: false }
},
// 리듀서 함수들
reducers: {
userReducer,
postsReducer,
uiReducer
},
// 구독자 목록
listeners: [component1, component2, ...]
};
// 브라우저 JavaScript Engine 메모리 구조
/*
JavaScript Heap Memory
├── Redux Store State (Global Variable)
│ ├── user: { ... }
│ ├── posts: [ ... ]
│ └── ui: { ... }
├── React Component Instances
├── Event Listeners
└── Closures
*/
// 비효율적인 구조 (중복 데이터)
const badState = {
posts: [
{ id: 1, title: 'Post 1', author: { id: 1, name: 'John' } },
{ id: 2, title: 'Post 2', author: { id: 1, name: 'John' } } // 중복!
]
};
// 정규화된 구조 (메모리 효율적)
const goodState = {
entities: {
users: {
1: { id: 1, name: 'John' }
},
posts: {
1: { id: 1, title: 'Post 1', authorId: 1 },
2: { id: 2, title: 'Post 2', authorId: 1 }
}
},
ui: {
postIds: [1, 2]
}
};
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistConfig = {
key: 'root',
storage,
// 메모리 사용량이 큰 데이터는 제외
blacklist: ['largeDataSets', 'temporaryUI']
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
"Redux Store는 브라우저의 JavaScript 힙 메모리에 전역 객체로 저장됩니다. 메인 스레드에서 동기적으로 동작하며, 모든 컴포넌트가 접근할 수 있습니다. 메모리 효율성을 위해 상태 정규화, Redux Persist를 통한 localStorage 활용, 대용량 데이터의 가상화 처리 등을 사용합니다."
React Native에서 Redux Store는 JavaScript Thread의 메모리에 저장되며, iOS/Android 네이티브 코드와는 분리된 공간에서 관리됩니다.
/*
React Native App Memory Architecture:
Native Heap (iOS/Android)
├── UI Thread (Main Thread)
├── Background Threads
└── JavaScript Context
├── JavaScript Heap
│ ├── Redux Store ← 여기에 저장
│ ├── React Components
│ └── Business Logic
└── Bridge Communication
*/
// React Native Redux 설정
import { createStore } from 'redux';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
const persistConfig = {
key: 'root',
storage: AsyncStorage, // Native 저장소 활용
whitelist: ['user', 'settings'] // 지속할 상태 선택
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);
// Redux Persist + AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage';
// 메모리와 영구 저장소 동기화
const persistConfig = {
key: 'root',
storage: AsyncStorage,
transforms: [
// 대용량 데이터 압축
createTransform(
(inboundState) => JSON.stringify(inboundState),
(outboundState) => JSON.parse(outboundState),
{ whitelist: ['largeDataSets'] }
)
]
};
import { AppState } from 'react-native';
// 앱이 백그라운드로 갈 때 메모리 정리
class MemoryManager {
constructor(store) {
this.store = store;
this.setupAppStateListener();
}
setupAppStateListener() {
AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'background') {
this.cleanupMemory();
} else if (nextAppState === 'active') {
this.restoreMemory();
}
});
}
cleanupMemory() {
// 임시 데이터 정리
this.store.dispatch({
type: 'CLEANUP_TEMPORARY_DATA'
});
}
}
"React Native에서 Redux Store는 JavaScript Thread의 힙 메모리에 저장됩니다. 네이티브 코드와는 분리된 공간에서 동작하며, AsyncStorage를 통해 영구 저장소와 연동할 수 있습니다. 앱이 백그라운드로 갈 때 메모리 정리, 디바이스 성능에 따른 동적 캐시 크기 조정 등 모바일 환경에 특화된 최적화가 필요합니다."
React Native는 JavaScript로 작성된 코드를 iOS/Android 네이티브 앱으로 변환하는 프레임워크입니다.
/*
React Native 3-Thread 구조:
1. Main Thread (UI Thread)
├── 네이티브 UI 렌더링
├── 사용자 인터랙션 처리
└── 애니메이션 실행
2. JavaScript Thread
├── React 컴포넌트 실행
├── 비즈니스 로직 처리
└── Redux/State 관리
3. Shadow Thread (Background)
├── Layout 계산 (Yoga Layout Engine)
├── UI 변경사항 계산
└── Native UI와 동기화
*/
// JavaScript → Native 통신
import { NativeModules } from 'react-native';
// JavaScript에서 네이티브 모듈 호출
NativeModules.LocationModule.getCurrentLocation()
.then(location => {
console.log('Current location:', location);
});
// Native → JavaScript 통신
import { NativeEventEmitter } from 'react-native';
const locationEmitter = new NativeEventEmitter(NativeModules.LocationModule);
locationEmitter.addListener('LocationUpdate', (event) => {
console.log('Location updated:', event);
});
// 1. JSX 컴포넌트 작성
function MyComponent() {
return (
<View style={styles.container}>
<Text style={styles.text}>Hello World</Text>
<TouchableOpacity onPress={handlePress}>
<Text>Press me</Text>
</TouchableOpacity>
</View>
);
}
// 2. React Native 처리 과정
/*
JSX → React Element → Shadow Tree → Native View
1. JSX가 React.createElement() 호출로 변환
2. Virtual DOM과 유사한 Shadow Tree 생성
3. Yoga Layout Engine이 레이아웃 계산
4. Bridge를 통해 네이티브 UI 컴포넌트 생성
- <View> → UIView (iOS) / ViewGroup (Android)
- <Text> → UILabel (iOS) / TextView (Android)
- <TouchableOpacity> → UIButton (iOS) / Button (Android)
*/
// metro.config.js
module.exports = {
resolver: {
alias: {
'@components': './src/components',
'@utils': './src/utils',
},
},
};
// JavaScript 번들 생성 과정
/*
1. Metro가 entry point (index.js)부터 dependency graph 생성
2. Babel로 JSX와 ES6+ 코드를 ES5로 변환
3. 모든 모듈을 하나의 번들 파일로 결합
4. 네이티브 앱이 번들을 로드하여 JavaScript Context 생성
*/
"React Native는 3개의 스레드로 동작합니다: UI 스레드(네이티브 UI), JavaScript 스레드(React 로직), Shadow 스레드(레이아웃 계산). JSX로 작성된 컴포넌트는 Shadow Tree를 거쳐 Yoga Layout Engine이 레이아웃을 계산하고, Bridge를 통해 네이티브 UI 컴포넌트로 변환됩니다. Metro Bundler가 JavaScript 코드를 번들링하여 네이티브 앱에서 실행합니다."
// android/app/build.gradle
project.ext.react = [
enableHermes: true, // 기본값이 true로 변경
]
// Hermes 엔진의 장점
/*
1. 앱 시작 시간 개선 (최대 50% 단축)
2. 메모리 사용량 감소 (최대 30% 절약)
3. 앱 크기 감소
4. JavaScript 실행 성능 향상
*/
// New Architecture 활성화 설정
// android/gradle.properties
newArchEnabled=true
// iOS 설정 - ios/Podfile
use_frameworks! :linkage => :static
$RNNewArchEnabled = true
// React 18 Concurrent Features 지원
import { startTransition } from 'react';
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (newQuery) => {
setQuery(newQuery); // 즉시 업데이트
startTransition(() => {
searchAPI(newQuery).then(setResults);
});
};
}
// metro.config.js - React Native 0.70 최적화
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true, // 성능 개선
},
}),
},
serializer: {
createModuleIdFactory: () => {
return (path) => {
return path.replace(/^.*\/node_modules\//, '');
};
},
},
};
"React Native 0.70의 주요 특징은 Hermes 엔진 기본 활성화로 앱 시작 시간이 50% 개선되고, New Architecture(Fabric, TurboModules) 기본 지원으로 성능이 크게 향상되었습니다. React 18의 Concurrent Features 지원으로 사용자 인터랙션이 더 부드러워졌습니다."
Bridge는 JavaScript 코드와 네이티브 플랫폼(iOS/Android) 간의 통신을 담당하는 핵심 구조입니다.
/*
Bridge Communication Flow:
JavaScript Thread Bridge Native Thread
| | |
1. Method Call → Serialize → Parse & Execute
2. Data Preparation → JSON Message → Native Function
3. Callback Setup ← Serialize ← Return Value
4. Result Processing ← JSON Response ← Callback
*/
// JavaScript에서 네이티브 모듈 호출
import { NativeModules } from 'react-native';
NativeModules.CameraModule.takePicture({
quality: 0.8,
format: 'jpeg'
})
.then(result => {
console.log('Photo saved:', result.uri);
})
.catch(error => {
console.error('Camera error:', error);
});
// 문제: 큰 데이터의 반복적 전송
const badExample = () => {
largeArray.forEach(item => {
NativeModules.StorageModule.saveItem(item); // 매번 직렬화
});
};
// 해결: 배치 처리
const goodExample = () => {
NativeModules.StorageModule.saveBatch(largeArray); // 한 번에 처리
};
// Native Driver로 Bridge 우회
import { Animated } from 'react-native';
Animated.timing(fadeValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true // Bridge 우회
}).start();
/*
기존 Bridge의 한계:
1. 비동기 직렬화로 인한 지연
2. JSON 변환 오버헤드
3. 단방향 메시지 큐
4. 메모리 복사 비용
New Architecture (JSI + Fabric + TurboModules)의 해결책:
1. 동기적 직접 호출 가능
2. JSON 없이 객체 직접 전달
3. 양방향 실시간 통신
4. 메모리 공유 가능
*/
"React Native Bridge는 JavaScript와 네이티브 코드 간의 비동기 통신을 담당합니다. JSON 직렬화를 통해 메시지를 전달하며, 주요 성능 이슈는 직렬화 오버헤드와 병목 현상입니다. 배치 처리와 Native Driver 사용으로 완화할 수 있으며, New Architecture의 JSI는 Bridge를 우회하여 동기적 직접 호출을 가능하게 합니다."
React Native New Architecture는 성능과 개발자 경험을 개선하기 위한 근본적인 아키텍처 재설계입니다.
// JSI를 통한 직접 호출
// 기존 Bridge 방식
NativeModules.LocationModule.getCurrentLocation()
.then(location => console.log(location)); // 비동기, JSON 직렬화
// JSI 방식 (동기 호출 가능)
const location = global.LocationModule.getCurrentLocationSync(); // 즉시 반환
console.log(location); // 바로 사용 가능
// TurboModule 정의 (TypeScript)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface LocationSpec extends TurboModule {
getCurrentLocation(): Promise<{
latitude: number;
longitude: number;
}>;
// 동기 메서드도 지원
getCurrentLocationSync(): {
latitude: number;
longitude: number;
};
}
export default TurboModuleRegistry.getEnforcing<LocationSpec>('LocationModule');
// Fabric을 통한 네이티브 컴포넌트
// 기존 방식: Bridge를 통한 비동기 렌더링
// Fabric: C++에서 직접 Shadow Tree 조작
function OptimizedComponent() {
// Fabric Renderer가 더 효율적으로 처리
return (
<View style={styles.container}>
<Text style={styles.text}>Hello Fabric!</Text>
<CustomNativeComponent />
</View>
);
}
// 동시성 렌더링 지원
import { startTransition } from 'react';
function App() {
const [urgent, setUrgent] = useState('');
const [deferred, setDeferred] = useState([]);
const handleInput = (text) => {
setUrgent(text); // 긴급 업데이트
startTransition(() => {
setDeferred(searchResults(text)); // 지연 가능한 업데이트
});
};
}
// 기존: JSON 직렬화로 메모리 복사
const data = { /* large object */ };
NativeModules.ProcessData.process(JSON.stringify(data)); // 메모리 복사
// New Architecture: 직접 객체 전달
const data = { /* large object */ };
TurboModules.ProcessData.process(data); // 메모리 공유
// TurboModule 인터페이스로 타입 안전성 보장
interface CameraSpec extends TurboModule {
takePicture(options: {
quality: number;
format: 'jpeg' | 'png';
}): Promise<{ uri: string; size: number }>;
}
// 컴파일 타임에 타입 체크
const camera = TurboModuleRegistry.get<CameraSpec>('Camera');
const result = await camera.takePicture({
quality: 0.8,
format: 'jpeg' // 타입 체크됨
});
// New Architecture 활성화
// android/gradle.properties
newArchEnabled=true
// iOS - ios/Podfile
use_frameworks! :linkage => :static
$RNNewArchEnabled = true
// 기존 네이티브 모듈을 TurboModule로 변환
// 1. TypeScript 인터페이스 정의
// 2. Codegen으로 네이티브 코드 생성
// 3. 네이티브 구현체 업데이트
"React Native New Architecture는 JSI, TurboModules, Fabric Renderer로 구성됩니다. JSI는 Bridge 없이 JavaScript와 네이티브 간 직접 통신을 가능하게 하고, TurboModules는 타입 안전한 네이티브 모듈 인터페이스를 제공합니다. Fabric Renderer는 C++에서 직접 Shadow Tree를 조작하여 렌더링 성능을 크게 개선합니다. 동기 호출, 메모리 공유, 타입 안전성이 주요 장점입니다."
// 문제: Bridge 병목 현상
// 많은 데이터를 자주 전송할 때 성능 저하
const badExample = () => {
data.forEach(item => {
NativeModules.Analytics.trackEvent(item); // 개별 호출
});
};
// 해결: 배치 처리
const goodExample = () => {
NativeModules.Analytics.trackEvents(data); // 배치 호출
};
// 메모리 누수 문제와 해결
import { useEffect } from 'react';
function LeakyComponent() {
useEffect(() => {
const timer = setInterval(() => {
// 메모리 누수 발생 가능
}, 1000);
// 클린업 누락으로 인한 메모리 누수
}, []);
}
function FixedComponent() {
useEffect(() => {
const timer = setInterval(() => {
// 작업 수행
}, 1000);
return () => clearInterval(timer); // 클린업 필수
}, []);
}
// iOS와 Android 차이점 처리
import { Platform } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 20 : 0, // 상태바 높이 차이
elevation: Platform.OS === 'android' ? 5 : 0, // Android 그림자
shadowOpacity: Platform.OS === 'ios' ? 0.3 : 0, // iOS 그림자
}
});
// 플랫폼별 컴포넌트 분리
import MyComponentIOS from './MyComponent.ios';
import MyComponentAndroid from './MyComponent.android';
const MyComponent = Platform.select({
ios: MyComponentIOS,
android: MyComponentAndroid,
});
// React Native 버전별 호환성 체크
import { NativeModules, Platform } from 'react-native';
const checkCompatibility = () => {
if (Platform.constants.reactNativeVersion.major >= 0.70) {
// New Architecture 사용
return 'newArch';
} else {
// Legacy Bridge 사용
return 'legacy';
}
};
// 조건부 라이브러리 로딩
const LocationModule = Platform.select({
ios: () => require('react-native-location-ios'),
android: () => require('react-native-location-android'),
})();
// Flipper를 통한 디버깅 설정
// react-native.config.js
module.exports = {
dependencies: {
'react-native-flipper': {
platforms: {
android: {
sourceDir: '../node_modules/react-native-flipper/android',
packageImportPath: 'com.facebook.flipper.reactnative',
},
},
},
},
};
// Redux DevTools 연동
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(
rootReducer,
composeWithDevTools()
);
"React Native의 주요 이슈는 Bridge 병목 현상, 플랫폼별 차이점, 라이브러리 호환성, 디버깅 어려움입니다. Bridge 병목은 배치 처리와 Native Driver로 해결하고, 플랫폼 차이는 Platform API로 조건부 처리합니다. 라이브러리 호환성은 버전 체크와 조건부 로딩으로, 디버깅은 Flipper와 Redux DevTools로 해결할 수 있습니다."
// 문제: 고해상도 이미지로 인한 메모리 부족
import { Image, Dimensions } from 'react-native';
const { width } = Dimensions.get('window');
// 나쁜 예: 원본 이미지 로드
<Image
source={{uri: 'https://example.com/large-image.jpg'}}
style={{width: 100, height: 100}}
/>
// 좋은 예: 적절한 크기의 이미지 로드
import FastImage from 'react-native-fast-image';
<FastImage
source={{
uri: 'https://example.com/optimized-image.jpg',
priority: FastImage.priority.normal,
}}
style={{width: 100, height: 100}}
resizeMode={FastImage.resizeMode.cover}
/>
// 이미지 캐시 관리
const clearImageCache = () => {
FastImage.clearMemoryCache();
FastImage.clearDiskCache();
};
// 문제: 대용량 리스트로 인한 메모리 과사용
import { FlatList } from 'react-native';
// 최적화된 FlatList 사용
function OptimizedList({ data }) {
const renderItem = useCallback(({ item }) => (
<ListItem item={item} />
), []);
const getItemLayout = useCallback((data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
getItemLayout={getItemLayout}
removeClippedSubviews={true} // 화면 밖 뷰 제거
maxToRenderPerBatch={10} // 배치당 렌더링 수 제한
windowSize={10} // 유지할 아이템 창 크기
initialNumToRender={10} // 초기 렌더링 수
/>
);
}
// useEffect 클린업
import { useEffect, useRef } from 'react';
function Component() {
const timerRef = useRef(null);
const subscriptionRef = useRef(null);
useEffect(() => {
// 타이머 설정
timerRef.current = setInterval(() => {
// 작업 수행
}, 1000);
// 이벤트 구독
subscriptionRef.current = EventEmitter.addListener('event', handler);
return () => {
// 클린업 - 메모리 누수 방지
if (timerRef.current) {
clearInterval(timerRef.current);
}
if (subscriptionRef.current) {
subscriptionRef.current.remove();
}
};
}, []);
}
// React.memo와 useMemo 사용
const ExpensiveComponent = React.memo(({ data }) => {
const expensiveValue = useMemo(() => {
return data.reduce((acc, item) => acc + item.value, 0);
}, [data]);
return <Text>{expensiveValue}</Text>;
});
import { AppState } from 'react-native';
class MemoryManager {
constructor() {
this.appStateSubscription = null;
this.setupAppStateListener();
}
setupAppStateListener() {
this.appStateSubscription = AppState.addEventListener(
'change',
this.handleAppStateChange
);
}
handleAppStateChange = (nextAppState) => {
if (nextAppState === 'background') {
this.freeMemory();
} else if (nextAppState === 'active') {
this.restoreMemory();
}
};
freeMemory() {
// 캐시 정리
FastImage.clearMemoryCache();
// 임시 데이터 제거
global.tempCache = null;
// 불필요한 리스너 해제
this.pauseLocationTracking();
}
restoreMemory() {
// 필요한 데이터 복원
this.resumeLocationTracking();
}
cleanup() {
if (this.appStateSubscription) {
this.appStateSubscription.remove();
}
}
}
// 메모리 사용량 추적
import { DevSettings } from 'react-native';
const monitorMemory = () => {
if (__DEV__) {
setInterval(() => {
if (global.performance && global.performance.memory) {
console.log('Memory Usage:', {
used: `${Math.round(global.performance.memory.usedJSHeapSize / 1048576)} MB`,
total: `${Math.round(global.performance.memory.totalJSHeapSize / 1048576)} MB`,
limit: `${Math.round(global.performance.memory.jsHeapSizeLimit / 1048576)} MB`
});
}
}, 5000);
}
};
// 메모리 경고 처리 (iOS)
import { DeviceEventEmitter } from 'react-native';
DeviceEventEmitter.addListener('memoryWarning', () => {
console.warn('Memory warning received');
// 메모리 정리 작업 수행
FastImage.clearMemoryCache();
});
"React Native 메모리 최적화는 이미지 캐싱(FastImage), 리스트 가상화(FlatList 최적화), 메모리 누수 방지(useEffect 클린업), 앱 상태 기반 관리로 구성됩니다. 실제 경험으로는 대용량 이미지 리스트에서 FastImage와 removeClippedSubviews를 사용해 메모리 사용량을 70% 감소시켰고, AppState 이벤트로 백그라운드 진입 시 불필요한 캐시를 정리해 OOM 크래시를 방지했습니다."
서버 사이드 렌더링(SSR)은 서버에서 React 컴포넌트를 HTML로 렌더링하여 클라이언트에 전송하는 기술입니다.
// 1. 서버에서 요청 받기
import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';
const server = express();
server.get('*', async (req, res) => {
try {
// 2. 필요한 데이터 미리 로드
const initialData = await fetchInitialData(req.url);
// 3. React 컴포넌트를 HTML 문자열로 렌더링
const appHTML = renderToString(
<App initialData={initialData} />
);
// 4. HTML 템플릿에 렌더링된 컴포넌트 삽입
const html = `
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<div id="root">${appHTML}</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`;
// 5. 완성된 HTML을 클라이언트에 전송
res.send(html);
} catch (error) {
res.status(500).send('Server Error');
}
});
// 클라이언트 진입점 (index.js)
import { hydrateRoot } from 'react-dom/client';
import App from './App';
// 1. 서버에서 전달받은 초기 데이터 사용
const initialData = window.__INITIAL_DATA__;
// 2. Hydration 수행 - 서버 렌더링된 HTML에 이벤트 리스너 등 연결
const container = document.getElementById('root');
hydrateRoot(container, <App initialData={initialData} />);
// 3. 이후 클라이언트 사이드에서 동작
delete window.__INITIAL_DATA__; // 메모리 정리
// App.js - 서버와 클라이언트 모두에서 동작
import { useEffect, useState } from 'react';
function App({ initialData }) {
const [data, setData] = useState(initialData || null);
const [loading, setLoading] = useState(!initialData);
useEffect(() => {
// 서버에서 데이터를 받았으면 추가 요청 안함
if (initialData) {
return;
}
// 클라이언트에서만 데이터 페칭
fetchData()
.then(setData)
.finally(() => setLoading(false));
}, [initialData]);
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{data.title}</h1>
<p>{data.content}</p>
</div>
);
}
// 서버에서 사용할 데이터 페칭 함수
export async function fetchInitialData(url) {
// URL에 따라 필요한 데이터 결정
const route = parseRoute(url);
switch (route.name) {
case 'user':
return await fetchUser(route.params.id);
case 'posts':
return await fetchPosts(route.params.page);
default:
return await fetchHomeData();
}
}
// pages/user/[id].js
export default function UserPage({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// 서버에서 실행되는 함수
export async function getServerSideProps(context) {
const { id } = context.params;
try {
// 1. 서버에서 데이터 페칭
const user = await fetchUser(id);
// 2. props로 컴포넌트에 전달
return {
props: {
user,
},
};
} catch (error) {
// 3. 에러 처리
return {
notFound: true,
};
}
}
// 또는 Static Generation (SSG)
export async function getStaticProps() {
const posts = await fetchPosts();
return {
props: {
posts,
},
revalidate: 60, // 60초마다 재생성
};
}
/*
렌더링 방식 비교:
1. Server-Side Rendering (SSR)
- 매 요청마다 서버에서 HTML 생성
- 초기 로딩 빠름, SEO 좋음
- 서버 부하 높음
2. Client-Side Rendering (CSR)
- 브라우저에서 JavaScript로 렌더링
- 초기 로딩 느림, SEO 어려움
- 서버 부하 낮음
3. Static Site Generation (SSG)
- 빌드 타임에 HTML 미리 생성
- 가장 빠른 로딩, SEO 좋음
- 동적 컨텐츠 한계
*/
// Hybrid 접근법 (Next.js)
export default function ProductPage({ product }) {
const [reviews, setReviews] = useState([]);
useEffect(() => {
// 제품 정보는 SSR, 리뷰는 CSR
fetchReviews(product.id).then(setReviews);
}, [product.id]);
return (
<div>
{/* SSR로 렌더링된 부분 */}
<h1>{product.name}</h1>
<p>{product.price}</p>
{/* CSR로 렌더링된 부분 */}
<ReviewList reviews={reviews} />
</div>
);
}
"React SSR은 서버에서 컴포넌트를 HTML로 렌더링하여 전송하는 과정입니다. 순서는: 1) 서버가 요청 수신, 2) 필요한 데이터 페칭, 3) renderToString으로 HTML 생성, 4) 초기 데이터와 함께 HTML 전송, 5) 클라이언트에서 hydration 수행입니다. 장점은 빠른 초기 로딩과 SEO 개선이고, 단점은 서버 부하와 복잡성 증가입니다. Next.js는 SSR/SSG/CSR을 페이지별로 선택할 수 있어 유연합니다."
이 문서는 React/React Native 면접 준비를 위한 실무 중심의 기술 가이드입니다. 각 섹션의 코드 예시와 개념 설명을 통해 실제 면접에서 활용할 수 있는 답변을 준비할 수 있습니다.