본문 바로가기
카테캠/2단계

[7/22] 무한 스크롤 옵저버

by 쪼꼬에몽 2025. 7. 22.

옵션 객체

fetchNextPage

- 마지막 요소가 보이면 호출할 함수 (다음 페이지 데이터 요청용)

hasMore

- 아직 불러올 데이터가 더 있는지 여부

loading

- 현재 데이터가 로딩 중인지 (중복 호출 방지용)

threshold

- IntersectionObserver의 감지 비율 (기본값: 0.1 = 10%)

observerRef

const observerRef = useRef<HTMLDivElement>(null);

- ref 객체를 반환하고, 이 ref를 화면 하단 요소에 붙이면 해당 요소가 뷰포트에 들어올 때 감지됨

- <div ref={observerRef} style={{ height: '1px' }} /> 같은 얇은 요소에 주로 붙임

옵저버 콜백 정의

const handleObserver = useCallback(
	(entries: IntersectionObserverEntry[]) => {
    	const target = entries[0];
        if (target.isIntersecting && hasMore && !loading) {
        	fetchNextPage();
        }
    },
    [fetchNextPage, hasMore, loading]
);

- IntersectionObserver가 감지되었을 때 실행되는 콜백 함수

- target.isIntersecting : 감시 대상 요소가 화면 안에 들어왔는지 여부

- hasMore && !loading : 다음 페이지가 존재하고, 현재 로딩 중이 아닐 때만 fetchNextPage() 호출

옵저버 등록

useEffect(() => {
	const observer = new IntersectionObserver(handleObserver, { threshold });
    
    const current = observerRef.current;
    if (current) observer.observe(current); // 요소가 있을 때만 관찰 시작
    
    return () => {
    	if (current) observer.unobserve(current); // 컴포넌트가 언마운트 시 관찰 해제
    };
}, [handleObserver, threshold]);

- IntersectionObserver 객체를 생성해 handleObserver를 등록함

- observerRef.current에 연결된 요소가 있을 경우, 해당 요소를 감시

- 컴포넌트가 언마운트되거나 threshold, handleObserver가 바뀌면 옵저버를 정리 

Intersection Observer

- DOM 요소가 다른 요소 또는 뷰포트와 교차하는지 감지하는 웹 API

- 요소가 화면에 보이기 시작하는 순간을 감지

- 무한 스크롤 : 페이지 하단에 도달했을 때 다음 데이터를 불러옴

- 이미지 Lazy Loading : 화면에 보일 때 이미지 로드

- 광고 또는 애니메이션 트리거 : 요소가 화면에 들어올 때 실행

const observer = new IntersectionObserver(entries => {
	entries.forEach(entry => {
    	if (entry.isIntersecting) {
        	console.log('요소가 화면에 나타남');
        }
    });
});

const target = document.querySelector('#target');
if (target) {
	observer.observe(target);
}

IntersectionObserver(callback, options)

- callback : 관찰 대상이 교차 상태가 바뀔 때 실행할 함수

- options : 설정값 (threshold, root, rootMargin 등)

- root : 관찰 기준이 되는 요소 (null이면 뷰포트 기준)

- threshold : 얼마나 겹쳤을 때 callback을 실행할 지 (0~1)

- rootMargin : root 기준에서 여유 공간 (ex. 100px)

callback 함수의 entries

- 관찰된 요소의 상태 정보를 담은 배열

- entry.isIntersecting : 대상 요소가 관찰 영역과 교차 중이면 true

- entry.intersectionRatio : 교차한 면적 비율 (0~1)

import { useCallback, useEffect, useRef } from 'react';

interface UseInfiniteScrollObserverOptions {
  fetchNextPage: () => void,
  hasMore: boolean;
  loading: boolean;
  threshold?: number;
}

export default function useInfiniteScrollObserver(
  {
    fetchNextPage,
    hasMore,
    loading,
    threshold = 0.1,
  }: UseInfiniteScrollObserverOptions,
) {
  const observerRef = useRef<HTMLDivElement>(null);

  const handleObserver = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      const target = entries[0];
      if (target.isIntersecting && hasMore && !loading) {
        fetchNextPage();
      }
    },
    [fetchNextPage, hasMore, loading]
  );

  useEffect(() => {
    const observer = new IntersectionObserver(handleObserver, { threshold });
    const current = observerRef.current;
    if (current) observer.observe(current);
    return () => {
      if (current) observer.unobserve(current);
    }
  }, [handleObserver, threshold]);

  return observerRef;
}

'카테캠 > 2단계' 카테고리의 다른 글

[7/22] React Query - useQuery  (0) 2025.07.22
[7/22] React Query  (1) 2025.07.22
[7/21] Intersection Observer  (1) 2025.07.22
[7/18] Fetch API Wrapping  (0) 2025.07.18
[7/17] local storage  (0) 2025.07.18