ScrollView는 모든 자식을 한 번에 렌더링하고, FlatList는 보이는 것만 렌더링합니다. 이 차이가 성능에 결정적 영향을 미칩니다.

면접에서 "ScrollView와 FlatList의 차이"를 물으면 가상화(virtualization) 키워드를 중심으로 답하면 됩니다.


핵심 차이 한눈에

항목ScrollViewFlatList
렌더링모든 자식을 ** 즉시** 렌더링보이는 영역만 ** 지연** 렌더링
데이터정적 콘텐츠동적 배열 데이터
성능아이템 적을 때 좋음아이템 많을 때 좋음
메모리전체 콘텐츠만큼 사용보이는 만큼만 사용
용도폼, 설정, 고정 콘텐츠피드, 목록, 검색 결과

ScrollView

TSX
import { ScrollView, View, Text, StyleSheet } from 'react-native';

function SettingsScreen() {
  return (
    <ScrollView
      style={styles.container}
      contentContainerStyle={styles.content}
      showsVerticalScrollIndicator={false}
    >
      {/* 고정된 수의 UI 요소들 */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>계정</Text>
        <SettingItem label="이름" value="심정훈" />
        <SettingItem label="이메일" value="test@email.com" />
      </View>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>알림</Text>
        <SettingItem label="푸시 알림" />
        <SettingItem label="이메일 알림" />
      </View>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>기타</Text>
        <SettingItem label="다크 모드" />
        <SettingItem label="언어" value="한국어" />
        <SettingItem label="앱 버전" value="1.0.0" />
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  content: { padding: 20 },
  section: { marginBottom: 24 },
  sectionTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 },
});

ScrollView 유용한 속성

TSX
<ScrollView
  // 스크롤 방향
  horizontal={false}

  // 스크롤 인디케이터
  showsVerticalScrollIndicator={false}

  // 페이징 (슬라이드 효과)
  pagingEnabled

  // 바운스 효과 (iOS)
  bounces={true}

  // 스크롤 이벤트
  onScroll={(event) => {
    const y = event.nativeEvent.contentOffset.y;
    console.log('스크롤 위치:', y);
  }}
  scrollEventThrottle={16}  // 16ms마다 이벤트 발생

  // 키보드 처리
  keyboardShouldPersistTaps="handled"
  keyboardDismissMode="on-drag"

  // 컨텐츠 최소 높이 (짧은 컨텐츠에서도 스크롤 영역 확보)
  contentContainerStyle={{ flexGrow: 1 }}
/>

ScrollView를 쓰면 안 되는 경우

TSX
// 이렇게 하면 1000개 아이템이 모두 한 번에 렌더링됨 → 메모리 폭발
function BadExample() {
  const data = Array.from({ length: 1000 }, (_, i) => `아이템 ${i}`);

  return (
    <ScrollView>
      {data.map((item, index) => (
        <View key={index} style={styles.item}>
          <Text>{item}</Text>
        </View>
      ))}
    </ScrollView>
  );
}

// FlatList로 변경하면 보이는 아이템만 렌더링
function GoodExample() {
  const data = Array.from({ length: 1000 }, (_, i) => ({
    id: String(i),
    label: `아이템 ${i}`,
  }));

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={styles.item}>
          <Text>{item.label}</Text>
        </View>
      )}
    />
  );
}

공부하다 보니 "아이템이 몇 개까지 ScrollView를 써도 괜찮나?"라는 질문이 많더라고요. 일반적으로 20~30개 이하 면 ScrollView가 괜찮고, 그 이상이면 FlatList를 쓰는 게 안전합니다.


FlatList가 적합한 경우

TSX
// 1. 서버에서 받아오는 동적 데이터
function FeedScreen() {
  const [posts, setPosts] = useState<Post[]>([]);

  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <PostCard post={item} />}
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
    />
  );
}

// 2. 검색 결과
function SearchResults({ results }: { results: SearchResult[] }) {
  return (
    <FlatList
      data={results}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ResultItem result={item} />}
      ListEmptyComponent={<Text>검색 결과가 없습니다</Text>}
    />
  );
}

ScrollView + FlatList 조합

화면 상단에 고정 콘텐츠가 있고, 아래에 리스트가 있는 패턴입니다.

TSX
// 방법 1: FlatList의 ListHeaderComponent 사용 (권장)
function ProfileScreen() {
  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <PostCard post={item} />}
      // 리스트 위에 고정 콘텐츠 배치
      ListHeaderComponent={() => (
        <>
          <ProfileHeader />
          <StatsSection />
          <TabSelector />
        </>
      )}
    />
  );
}

// 방법 2: ScrollView 안에 FlatList (비권장 — 성능 문제)
// 이렇게 하면 FlatList의 가상화가 무력화됨!
function BadCombination() {
  return (
    <ScrollView>
      <HeaderSection />
      {/* FlatList에 scrollEnabled={false}를 줘야 하므로 가상화 무의미 */}
      <FlatList
        data={data}
        renderItem={renderItem}
        scrollEnabled={false}
      />
    </ScrollView>
  );
}

ScrollView 안에 FlatList를 넣으면 경고가 뜨고 가상화가 제대로 동작하지 않습니다. 대신 FlatList의 ListHeaderComponent를 사용하세요.


성능 비교 실험

PLAINTEXT
데이터 100개 기준:
┌──────────────────────────────────────┐
│ ScrollView                           │
│ - 초기 렌더링: 100개 전부 → 느림     │
│ - 메모리: ~50MB                      │
│ - 스크롤: 부드러움                   │
├──────────────────────────────────────┤
│ FlatList                             │
│ - 초기 렌더링: 10개만 → 빠름         │
│ - 메모리: ~15MB                      │
│ - 스크롤: 부드러움 (설정에 따라)     │
└──────────────────────────────────────┘

데이터 1000개 기준:
┌──────────────────────────────────────┐
│ ScrollView                           │
│ - 초기 렌더링: 1000개 → 매우 느림    │
│ - 메모리: ~200MB+ → 크래시 위험      │
│ - 스크롤: 버벅거림                   │
├──────────────────────────────────────┤
│ FlatList                             │
│ - 초기 렌더링: 10개만 → 빠름         │
│ - 메모리: ~20MB (일정 유지)          │
│ - 스크롤: 최적화 설정에 따라 부드러움│
└──────────────────────────────────────┘

판단 기준 체크리스트

  • 아이템 수가 고정 이고 20개 이하 인가? → ScrollView
  • 아이템 수가 가변 이거나 **많을 수 있는가 **? → FlatList
  • 서버에서 데이터를 ** 페이지네이션 **으로 받는가? → FlatList
  • ** 풀-투-리프레시 **가 필요한가? → FlatList (RefreshControl 지원)
  • 폼이나 설정 화면처럼 ** 다양한 컴포넌트 혼합 **인가? → ScrollView
  • 같은 형태의 아이템이 ** 반복 **되는가? → FlatList

정리

  • ScrollView: 콘텐츠가 적고 고정적일 때 사용 (설정, 폼, 상세 화면)
  • FlatList: 데이터가 많거나 동적일 때 사용 (피드, 검색, 목록)
  • ScrollView 안에 FlatList를 넣지 마세요 — ListHeaderComponent를 대신 사용
  • 20~30개 이하 아이템은 ScrollView가 더 간단하고 충분합니다
  • 면접 핵심: "FlatList는 가상화로 보이는 것만 렌더링하여 메모리와 성능을 확보한다"
댓글 로딩 중...