React Native ScrollView: 혼합 콘텐츠 스크롤과 최적화 전략
ScrollView의 활용 패턴과 FlatList 대비 적절한 사용 시나리오 분석
React Native에서 긴 목록 데이터를 렌더링할 때 FlatList는 성능 최적화의 핵심 컴포넌트입니다. 수백, 수천 개의 항목을 렌더링해야 하는 상황에서 ScrollView와 달리 화면에 보이는 항목만 렌더링하는 가상화(Virtualization) 를 제공하여 메모리 사용량과 렌더링 성능을 대폭 개선합니다.
이 글에서는 FlatList의 내부 동작 원리와 최적화 전략, 그리고 실전 활용 패턴을 살펴봅니다.
ScrollView는 모든 자식 컴포넌트를 초기 렌더링 시점에 한 번에 렌더링합니다.
// [주의] 1000개 항목을 한 번에 렌더링
<ScrollView>
{items.map(item => (
<ItemComponent key={item.id} data={item} />
))}
</ScrollView>문제점:
FlatList는 현재 화면에 보이는 항목과 그 주변의 몇 개 항목만 렌더링합니다.
// [권장] 화면에 보이는 ~10개 항목만 렌더링
<FlatList
data={items}
renderItem={({ item }) => <ItemComponent data={item} />}
keyExtractor={item => item.id}
/>이점:
| 특징 | ScrollView | FlatList |
|---|---|---|
| 초기 렌더링 | 모든 항목 | 보이는 항목만 |
| 메모리 사용 | 항목 수에 비례 증가 | 거의 일정 |
| 1000개 항목 렌더링 시간 | ~3000ms | ~150ms |
| 적합한 사용 사례 | 고정된 소량 콘텐츠 | 동적 대용량 목록 |
FlatList는 두 가지 필수 props를 요구합니다:
<FlatList
data={dataSource} // 데이터 배열
renderItem={renderFunction} // 각 항목 렌더링 함수
/>import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
interface User {
id: string;
name: string;
email: string;
}
const users: User[] = [
{ id: '1', name: 'Alice', email: 'alice@example.com' },
{ id: '2', name: 'Bob', email: 'bob@example.com' },
// ... 수백 개 항목
];
const UserList = () => {
return (
<FlatList
data={users}
keyExtractor={(item) => item.id} // 고유 키 지정
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.email}>{item.email}</Text>
</View>
)}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
name: {
fontSize: 16,
fontWeight: 'bold',
},
email: {
fontSize: 14,
color: '#666',
},
});windowSize는 화면에 보이는 영역 대비 얼마나 많은 항목을 미리 렌더링할지 결정합니다.
<FlatList
data={items}
renderItem={renderItem}
windowSize={5} // 기본값: 21 (현재 화면의 위/아래로 10배씩)
/>windowSize 설정 가이드:
windowSize={5}: 빠른 스크롤 시 빈 공간 발생 위험, 메모리 최소화windowSize={21} (기본): 균형잡힌 설정windowSize={50}: 부드러운 스크롤, 메모리 사용량 증가항목의 높이가 고정되어 있다면 getItemLayout을 사용하여 측정 과정을 생략할 수 있습니다.
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={(data, index) => ({
length: 80, // 항목 높이
offset: 80 * index, // 항목의 Y 위치
index,
})}
/>성능 이점:
<FlatList
data={items}
renderItem={renderItem}
ListEmptyComponent={() => (
<View style={styles.empty}>
<Text>데이터가 없습니다</Text>
</View>
)}
/><FlatList
data={items}
renderItem={renderItem}
ListHeaderComponent={() => (
<Text style={styles.header}>사용자 목록</Text>
)}
ListFooterComponent={() => (
<Text style={styles.footer}>총 {items.length}명</Text>
)}
/><FlatList
data={items}
renderItem={renderItem}
ItemSeparatorComponent={() => (
<View style={styles.separator} />
)}
/>
const styles = StyleSheet.create({
separator: {
height: 1,
backgroundColor: '#ccc',
},
});데이터를 논리적 섹션으로 나눌 때는 SectionList를 사용합니다.
import { SectionList } from 'react-native';
const sections = [
{
title: '관리자',
data: [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
],
},
{
title: '일반 사용자',
data: [
{ id: '3', name: 'Charlie' },
{ id: '4', name: 'David' },
],
},
];
<SectionList
sections={sections}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.name}</Text>}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
/>iOS UITableView와 유사한 섹션 구조 제공
const [items, setItems] = useState<Item[]>(initialItems);
const [loading, setLoading] = useState(false);
const loadMore = async () => {
if (loading) return;
setLoading(true);
const newItems = await fetchMoreItems();
setItems(prev => [...prev, ...newItems]);
setLoading(false);
};
<FlatList
data={items}
renderItem={renderItem}
onEndReached={loadMore}
onEndReachedThreshold={0.5} // 50% 지점에서 트리거
ListFooterComponent={loading ? <ActivityIndicator /> : null}
/>const [refreshing, setRefreshing] = useState(false);
const onRefresh = async () => {
setRefreshing(true);
const newData = await fetchFreshData();
setItems(newData);
setRefreshing(false);
};
<FlatList
data={items}
renderItem={renderItem}
refreshing={refreshing}
onRefresh={onRefresh}
/>FlatList는 React Native에서 대용량 데이터를 효율적으로 렌더링하기 위한 필수 컴포넌트입니다. 가상화 메커니즘을 통해 수천 개의 항목도 부드럽게 처리할 수 있으며, getItemLayout, windowSize 같은 최적화 옵션으로 성능을 더욱 향상시킬 수 있습니다.
핵심 권장사항:
keyExtractor로 고유 키 지정 필수getItemLayout 활용참고 자료:
ScrollView의 활용 패턴과 FlatList 대비 적절한 사용 시나리오 분석
제스처 응답 시스템의 생명 주기와 다중 컴포넌트 간 터치 이벤트 협상 전략