IntersectionObserver와 debounce 방식
리액트에서 무한스크롤은 IntersectionObserver api를 사용해서 구현할 수도 있고, debounce 기반 스크롤 이벤트 감지로 구현할 수도 있다.
IntersectionObserver api 사용 방식
1단계: 상태 및 ref 준비
const [items, setItems] = useState<YourItemType[]>([]);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const containerRef = useRef<HTMLDivElement | null>(null); // 하단 감지용 ref
2단계: 데이터 패칭 함수 만들기
const fetchNextPage = async () => {
setIsLoading(true);
try {
const res = await getNextPageResults(page); // 예: `/api/items?page=${page}`
setItems((prev) => [...prev, ...res.items]);
setPage((prev) => prev + 1);
} catch (err) {
console.error("데이터 로딩 실패", err);
} finally {
setIsLoading(false);
}
};
3단계: IntersectionObserver 설정
useEffect(() => {
if (!containerRef.current) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && !isLoading) { // isLoading: 중복 호출 방지
fetchNextPage();
}
},
{ threshold: 1 } // 요소가 화면에 완전히 들어왔을 때 감지
);
observer.observe(containerRef.current);
return () => {
observer.disconnect();
};
}, [containerRef, items]);
4단계: ref를 리스트 마지막 요소 아래에 배치
<div className="item-list">
{items.map((item, index) => (
<Fragment key={index}>
<ItemCard item={item} />
{index === items.length - 1 &&
(<div ref={containerRef} className="h-10" />)} {/* 하단 감지용 */}
</Fragment>
))}
</div>
5단계: 로딩 UI 추가 (선택)
{isLoading && <div className="text-center text-sm py-4">Loading more...</div>}
debounce 기반 스크롤 이벤트 감지 방식
let debounceTimer = null
const onScroll = () => {
if (debounceTimer) clearTimeout(debounceTimer) // 너무 스크롤 이벤트 많이 발생하는 것 방지
debounceTimer = setTimeout(() => {
console.log('scroll')
const nearBottom =
window.innerHeight + window.scrollY + 100 > document.documentElement.offsetHeight
if (nearBottom) {
movieStore.getMovieMore(++page, type, keyword)
}
}, 200)
}
useEffect(()=>{
window.addEventListener('scroll', onScroll)
}, [])
Leave a comment