React 무한 스크롤의 성능 문제를 해결하는 방법: Dynamic Window

MNIII
2 min readJan 1, 2025

--

React로 대규모 목록을 구현할 때 가장 까다로운 문제 중 하나는 무한 스크롤의 성능 최적화입니다. SNS나 실시간 채팅 기능을 구현해본 개발자라면 이런 경험이 있을 것입이다.

“스크롤할 때마다 데이터를 불러오는 건 성공했는데… DOM에 계속 쌓이는 요소들이 신경쓰이네요.”

“확장 가능한 카드 UI를 만들었더니 스크롤이 튀어버리는 문제가 발생했어요.”

“새 메시지가 위에서 추가될 때마다 스크롤 위치가 흔들려요.”

이런 문제들을 해결하기 위해 여러 방법을 시도해보다가 React Dynamic Window 를 만들게 되었습니다. 아래 내용은 구현 과정에서 마주친 문제들과 해결 방법을 공유하려고 합니다.

가상화(Virtualization)의 구현 과정

화면에 보이는 요소만 렌더링 하기를 첫 번째로 시도했습니다.

const visibleItems = items.slice(startIndex, endIndex);

하지만 이 방식을 적용해보니 몇 가지 문제점이 드러났습니다:

  • 스크롤할 때마다 요소가 새로 생성되어 깜빡임 발생
  • 스크롤 위치 계산이 부정확
  • 부드러운 스크롤링 경험을 제공하지 못함

버퍼 도입으로 문제 해결하기

위 문제들을 해결하기 위해 버퍼 개념을 도입했습니다.

const bufferSize = 4; // 위아래로 4개씩 추가 렌더링
const start = Math.max(0, visibleStartIndex - bufferSize);
const end = Math.min(items.length, visibleEndIndex + bufferSize);

버퍼를 통해 스크롤 시의 깜빡임을 줄일 수 있었지만, 여전히 동적 높이 문제가 남아있었습니다.

ResizeObserver로 동적 높이 해결하기

각 아이템의 실제 높이를 정확하게 측정하고 관리하는 것이 중요했습니다.

useEffect(() => {
const resizeObserver = new ResizeObserver(updateHeight);
if (listItemRef.current) {
resizeObserver.observe(listItemRef.current);
}
return () => resizeObserver.disconnect();
}, [updateHeight]);

ResizeObserver를 사용함으로써

  • 컨텐츠가 변경될 때마다 정확한 높이 측정 가능
  • 확장/축소 시 자연스러운 애니메이션 구현 가능
  • 스크롤 위치 정확하게 유지 가능

양방향 무한 스크롤 구현의 도전

새로운 데이터가 위쪽에 추가될 때 스크롤 위치가 갑자기 변하는 문제를 해결했습니다.

useLayoutEffect(() => {
if (loadTypeRef.current === ENTRY_TYPE.PREPEND) {
const heightDiff = newItemsCount * itemHeight;
scrollElement.scrollTop = currentScrollTop + heightDiff;
}
}, [totalItems]);

실제 구현 사례: 채팅 애플리케이션

채팅 애플리케이션에서는 다음과 같은 요구사항이 있었습니다:

  • 메시지마다 다른 높이 (텍스트, 이미지, 링크 등)
  • 실시간으로 새로운 메시지 추가
  • 과거 메시지 로드
<ReactDynamicWindow
data={messages}
hasLatestData={hasNewMessages}
onLoadLatest={handleNewMessages}
onLoadMore={handlePastMessages}
>
{({ data, isExpanded }) => (
<ChatMessage
message={data}
isExpanded={isExpanded}
/>
)}
</ReactDynamicWindow>

Demo Preview

무한 스크롤은 단순해 보이지만 구현 과정에서 예상치 못한 여러 문제에 직면하게 됩니다. React Dynamic Window는 이러한 문제들을 해결하면서 만들어진 라이브러리 입니다.

비슷한 고민을 하고 계시다면 이 라이브러리를 한번 사용해 보시길 추천드립니다. 그리고 사용 과정에서 발견한 문제점이나 개선사항이 있다면 GitHub 저장소를 통해 의견을 나눠주세요. 피드백을 통해 더 나은 도구를 만들어갈 수 있을 것이라 생각합니다.

--

--

No responses yet