[React Native] SectionList to FlashList

Jiwoo JEONG·2023년 1월 21일
1
post-thumbnail

Sticky한 배너를 가지는 VirtualizedList

as-is

최강자 리그와 같은 배너가 스크롤할 때 해당 리그의 배너가 상단에 sticky하게 붙어야하는 기획이 있었다. 다음 영상처럼!
as-is.gif

첫번째 구현

위 움직이는 이미지처럼 구현하였다.

  • React Native에는 SectionList라는 VirtualizedList가 있어서 제공하는 sections={headers}stickySectionHeadersEnabled={true} props를 전달하여 header가 sticky하게 구현하였다.
  • 또한 내 위치로 이동을 통한 scrollOffset은 랭킹 아이템의 height과 header 배너의 height을 계산하여서 이동 시켜야했다.
  • keyExtractor, getItemLayout, initialNumToRender, maxToRenderPerBatch를 통해 최적화를 진행하였다.

🚫 하지만! 랭킹에 1,000명이 생기고 최적화한 값에는 header의 height을 지정할 수 없으니 오히려 헤더의 flickering이 발생하였다. 어떻게 해결해볼 수 있을까?
🚫 header를 위한 section을 만들기 위해서는 util을 통해 다음과 같은 구조도 만들기 위해 서버에서 받은 값을 변경해야했다.

groups : {
		// Sticky 헤더 위함
		header: {
          title: string;
          color: string;      
        }; 
  		// 해당 리그에 속한 사용자 list
  		users: {
          rank:number;
          name:string;
          imgUrl:string;
        }[];
	}[];  

React Native SectionList
https://reactnative.dev/docs/sectionlist#requiredsections

FlashList 도입

새로운 피쳐에서 헤더가 sticky하여야하는 비슷한 랭킹 UI가 있었다. FlashList를 팀원 분이 적용하는 것이 어떻겠냐는 아이디어를 제안하였다. 그리고 나는 기존에 개발 되어있지만 버그가 많은 전체 랭킹 페이지를 리팩토링하자는 제안을 함께 했다.

이전에 팀원 분께서 공유해주시기도 했고, React Native Seoul 11월 밋업에서도 관련 주제로 발표를 듣기도 했기 때문에 적용할 법하다고 생각했다.
estimatedItemSize를 header 배너의 height과 랭킹 아이템의 height 중 더 큰 것으로 전달하여 예측하는 아이템 사이즈를 전달하면 라이브러리에서 최적화를 알아서 해준다는 점이 간단하다고 느껴졌으며, 기존의 VirtualizedList의 ListEmptyComponent, ListHeaderComponent 등 비슷한 props가 있어서 러닝커브도 낮았다.

Most of the props from FlatList are available in FlashList, too. This documentation includes both FlatList and additional FlashList props and should be used as a primary reference. But you can also read more about the props available in both FlatList and FlashList here.
<FlashList 공식 문서 중 발췌>

결과

to-be

FlashList: SectionList

위와 비교해보았을 때 sticky 헤더의 flickering이 없어졌다. 또한 데이터 구조가 다음과 같이 1-depth로 구현할 수 있었다. 물론 이 때도 기존의 서버 데이터를 해당 데이터 타입으로 변경할 util이 필요하다.

UserType: {
          rank:number;
          name:string;
          imgUrl:string;
        };
LeagueType : {
		...리그 데이터,
        ranks:undefined;  
		};
       
ranks: (user: UserType | LeagueType ) [];

공식문서에서도 다음과 같이 적혀있다.

그리고 renderItem에서 UserType을 구분하는 isUserType util을 통해 render할 컴포넌트를 분기처리하였다.

FlashList를 사용하면서 느낀 점

✅ 확실히 코드의 양이 줄었다. 알아서 VirtualizedList를 최적화해주기 때문! 그리고 스크롤이 아이템이 많을 때도 부드럽다. 최적화 👍
✅ 코드를 조금 봤을 때 sticky 헤더를 기존의 FlatList + sticky 라이브러리를 통해 구현하였다. 아마 SectionList를 쓰지는 않은 듯!
✅ estimatedItemSize를 통해 정확한 아이템 사이즈 값이 아니어도 최적화를 해준다 !

🚫 ListHeaderComponent 관련 이슈가 있다. HeaderComponent가 있을 때 Lazy Render도 해보았으나, sticky한 첫번째 배너가 최상단에 렌더링 되는 이슈가 있었다.
🚫 scrollToIndex 관련 이슈가 있다. 3,000개 정도의 data가 있을 때 scrollToIndex가 정확한 위치로 찾아가지 못하는 문제가 있다.

결론: sticky 혹은 scrollToIndex를 사용하지 않는 SectionList를 구현할 때는 최적화 측면에서 비교불가 정도라고 느껴서 무조건 FlashList를 사용할 것 같다. 하지만 약간의 버그는 여전히 존재하며 더 보완하면 좋은 라이브러리가 될 것 같다. 위의 2가지의 이슈보다 최적화로 얻는 이점이 더 크다고 생각하여 적용을 결정하였다.

profile
FE Developer as Efficiency Maker

0개의 댓글