[React Native] Sticky Header

leave_a_comment·2026년 1월 25일
post-thumbnail

자사 서비스 개발을 하다 보니 Sticky Header UI를 구현할 일이 생각보다 많았다.
React Native에서는 성능 이슈가 쉽게 발생하는 만큼 리스트 출력에는 FlashList를 사용하며 최대한 최적화를 고려하고 있다.
이번 글에서는 FlashList + Sticky Header를 구현하면서 특히 애를 먹었던 부분들과, 그 과정에서 정리한 포인트들을 공유해보려고 한다.

첫 번째 시도

FlashList에서 제공하는 stickyHeaderIndices 옵션을 활용해 Sticky Header를 구현해보았다.
하지만 예상과는 다른 문제가 발생했다.

문제점

stickyHeaderIndicesrenderItem으로 렌더링되는 항목에만 적용되며
ListHeaderComponent에는 사용할 수 없었다.

그 결과 리스트의 첫 번째 상품 아이템이 고정되는 현상이 발생했고
의도했던 Sticky Header와는 전혀 다른 어색한 UI가 만들어졌다.

두번째 시도

ListHeaderComponent를 쓰지 말고 아예 Header를 renderItem 안으로 넣어버리자.

이를 위해 리스트 데이터에 타입을 부여해 각 아이템이 어떤 역할을 하는지 구분하도록 설계했다.

  • type: 'header' → Sticky Header로 사용될 영역
  • type: 'item' → 일반 상품 아이템

이렇게 하면 stickyHeaderIndices를 적용할 수 있고 Header 역시 리스트 아이템처럼 다룰 수 있을 것이라 기대했다.

하지만 이 방식은 FlashList의 구조적 특성과 잘 맞지 않았다.

FlashList 입장에서는 Header 역시 일반 아이템과 동일하게 취급되기 때문에

  • 재사용 대상이 되고
  • 스크롤에 따라 unmount/mount가 발생할 수 있으며
  • Sticky 상태에서 불필요한 re-render가 발생

결과적으로 FlashList의 가상화 장점을 일부 깨는 구조였다.

또한 성능 관점에서 보았을 때 이 방식이 최적은 아니라는 판단이 들었다.
FlashList는 기본적으로 아이템 가상화에 최적화된 리스트인데
stickyHeaderIndices는 레이아웃 계산, 오프스크린 아이템 유지,
스크롤 중 위치 보정 과정을 거치며 이로 인해 레이아웃 비용이 증가하기 때문이다.

세번째 시도

최종 구현 방식

FlashList에서 제공하는 스크롤 감지 옵션을 활용해 현재 스크롤 위치를 추적하도록 구현했다.

스크롤이 진행되면서 얻은 현재 Y축 위치를 기준으로 Header의 기준 위치와 비교하고
show / hide 여부에 따라 Header의 opacity를 조정하는 방식이다.

이를 통해 Sticky Header를 실제로 고정시키는 대신
스크롤 위치에 따라 자연스럽게 나타나고 사라지는 UI를 구현할 수 있었다.

결과적으로 레이아웃 재계산을 발생시키지 않으면서
JS → UI 변경 비용을 최소화해
성능과 UX를 모두 만족시키는 구현이 가능했다.

profile
나도 성장하고파

0개의 댓글