Skip to content

커서 기반 무한스크롤 공통 훅 도입 및 안정성 개선

heeyongKim edited this page Feb 24, 2026 · 11 revisions
  • PR: PR
  • 적용 대상: 피드, 띱 목록, 저장된 책, 댓글 등 커서 기반 목록 화면

🗒️ 개요

기존 무한스크롤은 페이지별 구현 방식이 달라(스크롤 이벤트 기반/IntersectionObserver 기반 혼재), 로딩 상태·중복 요청 방지·커서 처리 로직이 중복되면서 유지보수 비용이 증가하고 버그 재발 가능성이 있었습니다.

이를 해결하기 위해 커서 기반 무한스크롤 로직을 공통 훅 useInifinieScroll로 추상화하여, 동작 방식과 상태 관리 패턴을 통일했습니다.


📑 목차

  1. 문제 상황
  2. 해결 방안
  3. 훅 책임 범위
  4. 훅 구조
  5. 실행 흐름
  6. 안정성 설계 포인트(useRef 활용)
  7. 적용
  8. IntersectionObserver 선택 이유
  9. 기대 효과
  10. 성능 측정 결과

1. 문제 상황

기존 무한스크롤 구현은 화면마다 방식이 달랐습니다.

  • 일부 화면: window scroll 이벤트 기반
  • 일부 화면: IntersectionObserver 기반
  • 로딩 상태 / 중복 요청 방지 / 커서(nextCursor) / 마지막 페이지(isLast) 처리 로직이 페이지별로 중복

이로 인해

  • 동일 기능 수정 시 여러 페이지를 함께 수정해야 함 (유지보수 비용 증가)
  • 중복 호출, 마지막 페이지 이후 호출, 의존성 루프 등 버그가 재발할 여지 존재

2. 해결 방안

커서 기반 무한스크롤을 공통 훅으로 추상화하여 구현을 통일했습니다.

  • 훅 이름: useInifinieScroll (요청 네이밍 그대로 적용)
  • 공통 훅이 책임지는 범위:
    • 첫 페이지 로드
    • 다음 페이지 로드
    • items, nextCursor, isLast, 로딩 상태 관리
    • IntersectionObserver 연결/해제
    • 중복 실행 요청 차단(동시 요청 락)

3. 훅 책임 범위

useInifinieScroll은 “목록 화면에서 반복되는 무한스크롤 패턴”을 통째로 캡슐화합니다.

  • 데이터 누적: items를 누적 관리
  • 페이지 상태: nextCursor, isLast 관리
  • 로딩 상태 분리: 첫 로딩(isLoading) / 추가 로딩(isLoadingMore)
  • 오류 상태: error 노출
  • 트리거 제공:
    기반 트리거 제공

4. 훅 구조

4.1 입력 옵션

  • enabled: 현재 탭/화면에서 무한스크롤 활성화 여부
  • reloadKey: 값 변경 시 목록 초기화 후 첫 페이지 재조회
  • fetchPage(cursor): 커서 기반 페이지 호출 함수
  • mergeItems(prev, next): 병합 커스터마이징(중복 제거 등)
  • rootMargin, threshold: observer 감지 조건 튜닝

4.2 내부 상태

  • items: 누적 데이터
  • nextCursor: 다음 호출 커서
  • isLast: 마지막 페이지 여부
  • isLoading: 첫 페이지 로딩
  • isLoadingMore: 추가 로딩
  • error: 에러 상태
  • sentinelRef: 감시 대상 요소 ref
  • isFetchingRef: 동시 요청 차단 락
  • fetchPageRef: 최신 fetch 함수 참조 보장

5. 실행 흐름

5.1 첫 페이지 로드

  1. enabled 활성 + reloadKey 변경 감지
  2. 리스트/커서/마지막 페이지 상태 초기화
  3. fetchPage(null) 호출
  4. items, nextCursor, isLast 갱신

5.2 추가 페이지 로드

  1. sentinelRef가 뷰포트에 진입
  2. isLast, nextCursor, isFetchingRef 조건 확인
  3. fetchPage(nextCursor) 호출
  4. mergeItems로 병합 후 상태 업데이트

5.3 정리(cleanup)

  • 컴포넌트 업데이트/언마운트 시 observer.disconnect()로 관찰 해제

6. 안정성 설계 포인트 (useRef 활용)

6.1 동시 요청 차단:

isFetchingRef

무한스크롤은 관찰 이벤트가 연속 발생할 수 있으므로, 동일 시점 중복 실행 요청 차단이 필요합니다.

  • isFetchingRef.current === true이면 추가 로딩을 즉시 중단
  • state가 아닌 ref를 사용하여 즉시 반영 + 불필요한 리렌더링 방지를 동시에 달성

6.2 의존성 루프 방지:

fetchPageRef

fetchPage 함수는 컴포넌트 렌더링마다 참조가 바뀔 수 있어, 이를 그대로 의존성에 넣으면,

  • useEffect가 다시 실행되며
  • 불필요한 재요청/루프가 발생할 수 있습니다.

따라서 fetchPageRef.current = fetchPage로 최신 함수만 참조하도록 하여

  • “최신 fetch 사용”은 보장하면서
  • “렌더마다 함수 참조 변경으로 인한 재요청 루프”는 방지했습니다.

7. 적용

적용 파일

  • src/hooks/useInifinieScroll.ts (공통 훅)
  • src/pages/feed/Feed.tsx
  • src/pages/feed/FollowerListPage.tsx
  • src/pages/searchBook/SearchBook.tsx
  • src/pages/notice/Notice.tsx
  • src/pages/mypage/SavePage.tsx
  • src/pages/memory/Memory.tsx
  • src/components/group/MyGroupModal.tsx
  • src/pages/groupSearch/GroupSearch.tsx
  • src/components/search/GroupSearchResult.tsx
  • src/components/search/GroupSearchResult.styled.ts
  • src/pages/feed/FeedDetailPage.tsx
  • src/components/common/Post/ReplyList.tsx
  • src/components/common/CommentBottomSheet/GlobalCommentBottomSheet.tsx

변경 결과

  1. 무한스크롤 공통 훅 도입
  • 기존 window scroll 이벤트 제거
  • 탭별(피드/내 피드) 무한스크롤을 동일 패턴으로 통일
  • 하단 sentinel
    진입 시 다음 목록 로드
  1. 무한스크롤 기능 추가 (댓글 무한스크롤 로직 구현)
  • src/components/common/Post/ReplyList.tsx
  • src/components/common/CommentBottomSheet/GlobalCommentBottomSheet.tsx

useInifinieScroll를 내부에 붙여서 댓글을 직접 페이징 조회 postId/postType를 받으면 무한스크롤 모드로 동작 하단 sentinel + isLoadingMore 스피너 추가 댓글/답글 삭제 시 reload()로 목록 재조회 부모 스크롤 컨테이너 대응용 rootRef 옵션도 추가 기존 방식(commentList, onReload 주입)도 그대로 호환되게 유지

  • src/pages/feed/FeedDetailPage.tsx

댓글을 부모에서 한 번에 불러오던 로직 제거 ReplyList에 postId, postType="FEED" 전달해서 내부 무한스크롤 사용 댓글 작성 성공 시 replyReloadKey 증가로 ReplyList 재조회 트리거


8. IntersectionObserver 선택 이유

  • 성능 효율: 스크롤 이벤트를 매 프레임 계산하지 않고 “진입 시점”에만 동작
  • 중복 호출 감소: 명확한 트리거(센티넬 진입) 기반으로 불필요한 조건 체크 감소
  • 구현 단순화: scrollTop/scrollHeight 계산 코드 제거 가능
  • 재사용성 향상: 페이지마다 동일한 sentinel 패턴으로 통일
  • 컨테이너 대응 유연성: 필요 시 root 변경으로 내부 스크롤 컨테이너도 대응 가능
  • 유지보수성 향상: 관찰/해제 lifecycle이 훅 내부로 캡슐화됨

9. 기대 효과

  • 무한스크롤 동작의 일관성 확보
  • 페이지별 중복 코드 감소 → 유지보수성 향상
  • 중복 실행 요청/마지막 페이지 이후 호출 등 안정성 개선
  • 신규 화면 추가 시 훅 재사용으로 개발 속도 향상

10. 성능 측정 결과

<측정 환경 및 조건>

  • 도구: Chrome DevTools → Performance
  • 측정 구간: 동일 사용자 시나리오(예: 피드 진입 → 스크롤로 4 페이지 추가 로드) 수행 후, 타임라인에서 해당 구간 드래그 선택
  • 관측 지표
    • Total time: 선택 구간 총 시간 (10초)
    • Main thread time: 메인 스레드에서 실제로 바쁜 시간(체감 렉과 직결)
    • Scripting time: JS 실행/계산 시간(렌더 전에 수행되는 연산 포함)
    • Rendering/Painting: 렌더링/페인팅 비용

무한스크롤 성능 개선 전

  • Total: 10,000 ms
  • Scripting: 3,544 ms
  • Rendering: 153 ms
  • Painting: 106 ms
  • 스크립팅 작업 수행 시간: 3,544 / 10,000 * 100% = 약 35%

무한스크롤 성능 개선 후

  • Total: 10,000 ms
  • Scripting: 2,379 ms
  • Rendering: 166 ms
  • Painting: 120 ms
  • 스크립팅 작업 수행 시간: 2,379 / 10,000 * 100% = 약 24%

✅ 검사 결과

  • Main thread time
    • 큰 변화 없음
  • Scripting 시간 감소율
    • (3,544 - 2,379) / 3,544 * 100% = 32.87% (약 32.9% 감소)

✅ 해석

  • 무한스크롤 리팩토링 이후, 스크롤 이벤트 기반 반복 계산 시간이 감소했음(스크립팅 작업 수행 시간의 비중이 감소함)을 알 수 있음

Clone this wiki locally