무한스크롤을 만들때 주로사용하는 IntersectionObserver를 예시와 함께 알아봅니다.
전체 코드는 github를 참고해주세요.
const CardList: FC<{ data: Docs[]; fetchMore: () => void }> = ({
data,
fetchMore,
}) => {
const { lastItemRef } = useInfiniteScroll(fetchMore);
return (
<StyledCardList>
{data?.map((book, index) => {
if (index === data.length - 1) {
return (
<div ref={lastItemRef} key={index}>
<CardItem book={book} />
</div>
);
}
return <CardItem book={book} key={index} />;
})}
</StyledCardList>
);
};
import { useCallback, useRef } from 'react';
function useInfiniteScroll(fetchMore: () => any) {
const observerRef = useRef<IntersectionObserver | null>(null);
const lastItemRef = useCallback((node: HTMLDivElement) => {
if (observerRef.current) {
observerRef.current.disconnect();
}
observerRef.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// fetch를 하기위한 callback
fetchMore();
}
}, { rootMargin: '200% 0px' });
if (node) {
observerRef.current.observe(node);
}
}, []);
return {
lastItemRef,
};
}
export default useInfiniteScroll;
IntersectionObserver를 넣어둘 useRef를 생성합니다.
여기서의 useRef는 매번 새롭게 생성하는 IntersectionObserver를 담아둘 공간으로, 일반적으로 DOM을 참조하기 위한 목적이 아닙니다.
CardList 컴포넌트에서 마지막 CardItem에 ref에 lastItemRef를 넣어줍니다.
ref 함수 작성
<div ref={lastItemRef} key={index}>
는 <div ref={(ref) => lastItemRef(ref)} key={index}>
와 같습니다. 즉 node element를 받게 됩니다.기존 lastItem의 observe 중단
observerRef.current.disconnect();
로 모든관찰을 중단합니다.새로운 IntersectionObserver 생성
observerRef.current = new IntersectionObserver
으로 새로운 IntersectionObserver를 생성합니다. IntersectionObserver의 parameter는 두가지가 있습니다. (자세한 설명은 이 블로그를 참고해주세요.) IntersectionObserver의 첫번째 parameter
IntersectionObserver의 두번째 parameter
CardList를 생성할때 lastItemRef로 마지막 Item을 관찰합니다.
ref를 생성하면서 기존관찰을 모두 disconnect()
하고, 내부에 new IntersectionObserver()
를 생성합니다. 그리고 node를 관찰(observe(node)
)합니다.
관찰하던 node와 root의 교차(intersection)가 발생하면, IntersectionObserver의 callback이 실행됩니다.
callback함수에 의해 fetchMore이 실행됩니다.