Better List Views in React Native
여러분 중 많은 분들이 이미 커뮤니티 그룹의 티저 발표를 보고 새로운 List 컴포넌트를 사용해 보기 시작했지만, 오늘 공식적으로 발표한다! 더 이상 ListView
나 DataSource
, 오래된 행, 무시된 버그, 과도한 메모리 소비에 시달릴 필요가 없다. 최신 React Native 2017년 3월 릴리스 후보 버전(0.43-rc.1
)에서 여러분은 새로운 컴포넌트 세트 중에서 가장 적합한 것을 선택할 수 있으며, 뛰어난 성능과 즉시 사용 가능한 기능들을 제공한다:
<FlatList>
이 컴포넌트는 간단하면서도 고성능 리스트를 구현할 때 주로 사용한다. 데이터 배열과 renderItem
함수만 제공하면 바로 사용할 수 있다:
<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>
<SectionList>
데이터를 논리적인 섹션으로 나누어 렌더링하고 싶다면, 예를 들어 알파벳 순 주소록처럼 섹션 헤더를 추가하거나, 프로필 뷰처럼 다양한 데이터와 렌더링 방식을 혼합하여 사용하고 싶다면 (예: 버튼, 컴포저, 사진 그리드, 친구 그리드, 스토리 리스트 순서로 구성된 화면), 이 컴포넌트를 사용하면 된다.
<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // 섹션 간 동일한 렌더링 방식
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>
<SectionList
sections={[ // 섹션 간 다른 렌더링 방식
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>
<VirtualizedList>
보다 유연한 API를 제공하는 내부 구현체다. 데이터가 일반 배열이 아닌 경우(예: 불변 리스트)에 특히 유용하다.
기능
리스트는 다양한 상황에서 활용되므로, 새로운 컴포넌트에는 대부분의 사용 사례를 즉시 처리할 수 있는 다양한 기능을 포함했다:
- 스크롤 로딩 (
onEndReached
). - 당겨서 새로고침 (
onRefresh
/refreshing
). - 구성 가능한 가시성(VPV) 콜백 (
onViewableItemsChanged
/viewabilityConfig
). - 수평 모드 (
horizontal
). - 지능적인 아이템 및 섹션 구분자.
- 다중 컬럼 지원 (
numColumns
). scrollToEnd
,scrollToIndex
, 그리고scrollToItem
.- 향상된 Flow 타입 지정.
주의 사항
-
아이템 서브트리의 내부 상태는 렌더링 윈도우를 벗어나 스크롤될 때 유지되지 않는다. 모든 데이터를 아이템 데이터나 Flux, Redux, Relay 같은 외부 저장소에 저장해야 한다.
-
이 컴포넌트들은
PureComponent
를 기반으로 동작한다. 따라서props
가 얕은 비교(shallow-equal)에서 동일하다면 리렌더링되지 않는다.renderItem
함수가 직접 의존하는 모든 값이 업데이트 후===
비교에서 동일하지 않은 prop으로 전달되도록 해야 한다. 그렇지 않으면 UI가 변경 사항에 따라 업데이트되지 않을 수 있다. 이는data
prop과 부모 컴포넌트의 상태도 포함된다. 예를 들어:<FlatList
data={this.state.data}
renderItem={({item}) => (
<MyItem
item={item}
onPress={() =>
this.setState(oldState => ({
selected: {
// 새로운 인스턴스는 `===` 비교를 깨뜨림
...oldState.selected, // 기존 데이터 복사
[item.key]: !oldState.selected[item.key], // 토글
},
}))
}
selected={
!!this.state.selected[item.key] // renderItem은 state에 의존
}
/>
)}
selected={
// 기존 props와 충돌하지 않는 어떤 prop이라도 가능
this.state.selected // selected가 변경되면 FlatList가 리렌더링됨
}
/> -
메모리 사용을 제한하고 부드러운 스크롤을 가능하게 하기 위해, 콘텐츠는 오프스크린에서 비동기적으로 렌더링된다. 이는 스크롤 속도가 채우는 속도보다 빨라 잠깐 빈 콘텐츠가 보일 수 있음을 의미한다. 이는 각 애플리케이션의 필요에 따라 조정할 수 있는 트레이드오프이며, 내부적으로 이를 개선하기 위해 노력하고 있다.
-
기본적으로 이 새로운 리스트는 각 아이템의
key
prop을 찾아 React key로 사용한다. 또는 커스텀keyExtractor
prop을 제공할 수도 있다.
성능
새로운 리스트 컴포넌트는 API를 단순화할 뿐만 아니라, 성능 면에서도 상당한 개선을 이루었다. 가장 큰 장점은 행의 수와 상관없이 거의 일정한 메모리 사용량을 유지한다는 점이다. 이는 렌더링 윈도우 밖에 있는 엘리먼트를 '가상화'하여 컴포넌트 계층 구조에서 완전히 언마운트하고, React 컴포넌트의 JS 메모리와 섀도우 트리 및 UI 뷰의 네이티브 메모리를 회수함으로써 가능해졌다. 단, 이 경우 컴포넌트 내부 상태는 보존되지 않으므로, 중요한 상태는 컴포넌트 외부에서 관리해야 한다. 예를 들어 Relay, Redux, Flux 스토어 등을 활용할 수 있다.
렌더링 윈도우를 제한함으로써 React와 네이티브 플랫폼이 수행해야 하는 작업량도 줄어든다. 예를 들어 뷰 탐색 작업이 대표적이다. 수백만 개의 엘리먼트 중 마지막 항목을 렌더링하더라도, 새로운 리스트를 사용하면 모든 엘리먼트를 순회할 필요가 없다. scrollToIndex
를 사용해 중간으로 바로 이동할 수 있으며, 이 과정에서 과도한 렌더링이 발생하지 않는다.
또한 스케줄링 측면에서도 개선이 이루어져 애플리케이션의 반응성이 향상되었다. 렌더링 윈도우의 가장자리에 위치한 항목은 적은 빈도로 렌더링되며, 활성 제스처나 애니메이션, 기타 상호작용이 완료된 후 낮은 우선순위로 처리된다.
고급 활용
ListView
와 달리, 렌더링 윈도우 내의 모든 항목은 props가 변경될 때마다 리렌더링된다. 윈도우 기법이 항목 수를 일정하게 줄여주기 때문에 대부분의 경우 문제가 없지만, 항목이 복잡한 구조라면 성능을 위해 React의 모범 사례를 따르는 것이 좋다. React.PureComponent
를 사용하거나 컴포넌트 내에서 shouldComponentUpdate
를 적절히 활용해 재귀적 서브트리의 리렌더링을 제한할 수 있다.
항목을 렌더링하지 않고도 행의 높이를 계산할 수 있다면, getItemLayout
prop을 제공해 사용자 경험을 개선할 수 있다. 이렇게 하면 scrollToIndex
등을 사용해 특정 항목으로 스크롤할 때 훨씬 부드러운 동작을 보장하며, 콘텐츠의 높이를 렌더링 없이도 결정할 수 있어 스크롤 표시기 UI도 향상된다.
불변 리스트(immutable list)와 같은 대체 데이터 타입을 사용한다면 <VirtualizedList>
를 활용하는 것이 좋다. 이 컴포넌트는 getItem
prop을 제공해 주어진 인덱스에 해당하는 항목 데이터를 반환할 수 있으며, 타입 체크도 좀 더 유연하게 처리한다.
특이한 사용 사례가 있다면 조정할 수 있는 다양한 파라미터도 있다. 예를 들어 windowSize
를 사용해 메모리 사용량과 사용자 경험 사이의 균형을 조정하거나, maxToRenderPerBatch
로 렌더링 속도와 반응성의 균형을 맞출 수 있다. onEndReachedThreshold
를 사용해 스크롤 로딩이 발생하는 시점을 제어할 수도 있다.
향후 작업
- 기존 컴포넌트의 마이그레이션 (최종적으로
ListView
의 사용 중단). - 필요성에 따라 추가 기능 구현 (여러분의 의견을 보내주세요!).
- 스틱키 고정 섹션 헤더 지원.
- 더 많은 성능 최적화 작업.
- 상태를 가진 함수형 아이템 컴포넌트 지원.