성능 최적화 — 렌더링, 메모이제이션, 프로파일링
React Native 성능 문제의 80%는 불필요한 리렌더링에서 발생합니다.
리렌더링 추적
// 리렌더링 횟수 확인 (개발 중)
function useRenderCount(componentName: string) {
const count = useRef(0);
count.current += 1;
console.log(`[${componentName}] 렌더링 횟수: ${count.current}`);
}
function ExpensiveComponent() {
useRenderCount('ExpensiveComponent');
// ...
}
React.memo — 불필요한 리렌더링 방지
// props가 변경되지 않으면 리렌더링 스킵
const ListItem = React.memo(function ListItem({ title, onPress }: {
title: string;
onPress: () => void;
}) {
return (
<Pressable onPress={onPress} style={styles.item}>
<Text>{title}</Text>
</Pressable>
);
});
// 커스텀 비교 함수
const UserCard = React.memo(
function UserCard({ user }: { user: User }) {
return <Text>{user.name}</Text>;
},
(prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);
useCallback과 useMemo
function ParentComponent() {
const [items, setItems] = useState<Item[]>([]);
// useCallback — 함수 참조 안정화
const handlePress = useCallback((id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
}, []);
// useMemo — 비싼 계산 결과 캐싱
const totalPrice = useMemo(() => {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [items]);
// useMemo — 정렬된 리스트 캐싱
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => b.createdAt - a.createdAt);
}, [items]);
return (
<FlatList
data={sortedItems}
renderItem={({ item }) => (
<ListItem
title={item.title}
onPress={() => handlePress(item.id)}
/>
)}
/>
);
}
useCallback과useMemo를 무분별하게 쓰면 오히려 오버헤드가 됩니다. React.memo된 자식 컴포넌트에 전달하는 props 나 비싼 계산 에만 사용하세요.
FlatList 최적화
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
// 고정 높이일 때 스크롤 성능 향상
getItemLayout={(_, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
// 렌더링 배치 크기
maxToRenderPerBatch={10}
// 뷰포트 버퍼
windowSize={5}
// 초기 렌더링 수
initialNumToRender={10}
// 제거 지연
removeClippedSubviews={true}
/>
이미지 최적화
// 1. 적절한 크기의 이미지 사용
// 4000x3000 원본 대신 400x300 썸네일 사용
// 2. 캐싱 라이브러리 사용
import { Image } from 'expo-image'; // 자동 캐싱
// 3. 지연 로딩
<FlatList
data={images}
renderItem={({ item }) => (
<Image
source={{ uri: item.thumbnailUrl }} // 썸네일 먼저
style={{ width: 100, height: 100 }}
contentFit="cover"
transition={200}
/>
)}
/>
InteractionManager — 무거운 작업 지연
import { InteractionManager } from 'react-native';
function DetailScreen() {
const [data, setData] = useState(null);
useEffect(() => {
// 화면 전환 애니메이션이 끝난 후 데이터 로드
const task = InteractionManager.runAfterInteractions(async () => {
const result = await fetchHeavyData();
setData(result);
});
return () => task.cancel();
}, []);
return data ? <DetailContent data={data} /> : <ActivityIndicator />;
}
Flipper / Perf Monitor
// 개발 메뉴 → "Show Perf Monitor"
// JS 스레드와 UI 스레드의 FPS를 실시간으로 확인
// React DevTools Profiler
// 어떤 컴포넌트가 왜 리렌더링되었는지 분석
// Flipper
// - 네트워크 요청 모니터링
// - React DevTools 통합
// - 레이아웃 인스펙터
성능 체크리스트
- **JS 스레드 60fps 유지 **: 무거운 계산을 메인 스레드에서 분리
- **UI 스레드 60fps 유지 **:
useNativeDriver: true로 애니메이션 처리 - ** 불필요한 리렌더링 방지 **:
React.memo,useCallback,useMemo - **FlatList 최적화 **:
getItemLayout,windowSize,removeClippedSubviews - ** 이미지 최적화 **: 적절한 크기, 캐싱, 지연 로딩
- ** 번들 크기 최소화 **: 불필요한 라이브러리 제거, Tree Shaking
정리
- 성능 최적화는 ** 측정 먼저, 최적화 나중 **입니다 — Perf Monitor로 병목을 찾으세요
React.memo와useCallback은 ** 필요한 곳에만** 사용하세요- FlatList의
getItemLayout은 ** 스크롤 성능에 가장 큰 영향 **을 미칩니다 - 화면 전환 후 데이터 로딩에는
InteractionManager를 활용하세요 - 이미지는 ** 서버에서 적절한 크기로 제공 **하는 것이 가장 효과적입니다
댓글 로딩 중...