옵션 객체
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 |