LegendList는 @legendapp/list 라이브러리에서 제공하는, "FlashList의 대안"으로 등장한 최신 고성능 패키지이다.
React Native 생태계에서 리스트 컴포넌트의 발전 역사를 보면 3세대에 해당한다고 볼 수 있다.

FlashList는 혁명적이었지만, 완벽하지 않았다. 특히 스크롤을 아주 빠르게 하면 화면에 아이템이 그려지기 전에 스크롤이 먼저 되어서 하얀 빈 공간(Blank Space)이 보이는 문제가 있다. 또한 FlashList는 리스트 아이템들의 size가 거의 동일해야 최적화가 좋아지는데, 만약 sns의 feed 등 height의 길이가 다양하다면 ? 또 최적화 되지 않을 수도 있다.
LegendList는 이 문제를 해결하고, "동적 높이(Dynamic Height)" 아이템을 더 쉽게 다루기 위해 만들어졌다.
FlashList처럼 View Recycling(뷰 재활용) 기술을 기본으로 사용한다. 여기에 더해, @legendapp/state (같은 팀이 만든 상태 관리 라이브러리)의 철학을 빌려와, 리렌더링을 최소화하는 최적화가 내장되어 있다.
FlashList와 매우 비슷하다. API가 거의 호환된다.
import { LegendList } from '@legendapp/list';
const MyComponent = () => {
return (
<LegendList
data={data}
renderItem={({ item }) => <MyItem item={item} />}
// FlashList처럼 높이 추정치. 안 넣어줘도 알아서 예측해주긴 함
estimatedItemSize={100}
// [차별점] 뷰 재활용을 위한 ID 설정이 더 직관적임
keyExtractor={(item) => item.id}
/>
);
};
recycleItems
스크롤 밖으로 나간 아이템 컴포넌트를 재사용하는 옵션이다. 기본값은 false로, true로 설정하면 성능이 크게 향상되지만 아이템 컴포넌트 내부에 로컬 state가 있다면 예기치 않게 재사용될 수 있어 주의가 필요하다.
getEstimatedItemSize
아이템의 예상 크기를 반환하는 함수로, 리스트 레이아웃을 실제 렌더링 전에 미리 계산하는 데 사용된다. 제공하지 않으면 최적 성능을 위한 권장값을 로그로 알려준다.
getFixedItemSize
고정 크기인 아이템에 사용하며, 사이즈 측정 오버헤드를 없애 최적 성능을 낼 수 있다. 동적 크기 아이템은 undefined를 반환하면 된다.
ScrollView
FlashList
LegendList
useCallback 필요| Props | 이유 |
|---|---|
renderItem | 참조 변경 시 아이템 리렌더 유발 |
onEndReached | 스크롤 이벤트마다 호출, 참조 불안정하면 중복 트리거 가능 |
onRefresh | 리렌더마다 새 함수면 RefreshControl이 불안정하게 동작 가능 |
useMemo 필요| Props | 이유 |
|---|---|
refreshProps | 객체 리터럴은 매 렌더마다 새 참조 생성 |
contentContainerStyle | 스타일 객체도 동일 |
| Props | 이유 |
|---|---|
keyExtractor | 결과값만 쓰고 참조 비교 안 함 |
data | 보통 이미 외부 상태(query 등)에서 참조 안정성 보장됨 |
estimatedItemSize | 숫자 원시값, 항상 동일 참조 |
ItemSeparatorComponent | 컴포넌트 정의를 컴포넌트 외부에 선언하면 해결 |
ListEmptyComponent (컴포넌트인 경우) | 마찬가지로 외부 선언으로 해결 |
ItemSeparatorComponent, ListEmptyComponent의 올바른 패턴// ✅ 컴포넌트 외부 선언 → 항상 동일 참조, 메모이제이션 불필요
const ItemSeparator = () => <View />;
const ListEmpty = () => <CommonEmpty />;
export default function MyScreen() {
return (
<CommonLegendList
ItemSeparatorComponent={ItemSeparator}
ListEmptyComponent={ListEmpty}
/>
);
}
renderItem과 반드시 세트로// renderItem 아무리 안정화해도 아이템 컴포넌트에 memo 없으면 의미 반감
const MyItem = React.memo(function MyItem({ item }: Props) {
return <View />;
});
const renderItem = useCallback(({ item }: { item: Item }) => (
<MyItem item={item} />
), []);
useCallback(renderItem) + React.memo(아이템 컴포넌트) 는 항상 세트이다-
사실 renderItem도 FlashList 내부에서 어차피 다시 호출되기 때문에, useCallback으로 감싸는 것만으로는 셀 리렌더를 완전히 막지 못한다. 진짜 효과적인 건 renderItem 안에서 반환하는 컴포넌트 자체를 memo로 감싸는 것이다.
함수 호출 → JSX element 반환 → element끼리 === 비교
=== → false (참조 비교)
→ 내부 컴포넌트를 React.memo로 감싸야