흔히 Infinite Scroll
을 구현하는 방법으로는 Scroll Event
를 이용한 방식과 Intersection Observer
를 이용한 방식으로 나뉜다.
Scroll Event
로 구현한 방식은 사용자가 scroll을 할때마다 이벤트가 실행되기에 성능적으로 좋지 않다. (throttle 혹은 debounce로 최적화 필요)
이번 글에서는 이전에 프로젝트에서 사용했던 Intersection Observer
와 react-query
의 useInfiniteQuery
를 이용해 구현한 Infinite Scroll
을 정리해보려고한다.
IntersectionObserver(교차 관찰자 API) : 타겟 엘레멘트와 타겟의 부모 혹은 상위 엘레멘트의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API (즉, 화면상에 내가 지정한 타겟 엘레멘트가 보이고 있는지를 관찰하는 API)
먼저 IntersectionObserver
로 구현한 InfiniteScroll Custom Hook이다.
import { RefObject, useEffect, useState } from "react";
type UseInfiniteScrollProperties = {
readonly ref: RefObject<Element>;
readonly options?: UseInfiniteScrollOptions;
readonly callback: () => void;
};
type UseInfiniteScrollOptions = {
readonly threshold: number;
};
/**
* @hooks useInfiniteScroll
* InfiniteScroll Custom Hook with Intersection Observer
*/
export const useInfiniteScroll = ({ ref, callback, options = { threshold: 0 } }: UseInfiniteScrollProperties) => {
const handleInfiniteScroll = (entries: IntersectionObserverEntry[]) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
callback();
}
});
}
const [observer] = useState(new IntersectionObserver(handleInfiniteScroll, options));
useEffect(() => {
if (!observer) return
if (ref.current) {
observer?.observe(ref?.current);
}
return () => observer?.disconnect();
}, [observer, ref]);
}
threshold
: 옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시 ( 0 ~ 1.0 )
ref로 넘긴 부분이 threshold 만큼 교차될때, 인자로 넘긴 callback함수가 실행되는 Custom Hook이다.
이를 통하여, 리스트의 마지막 부분의 엘리먼트를 ref로 넘긴 후 스크롤이 내려갔을때 (해당 부분이 관찰되었을때) 다음 리스트를 불러오는 callback을 실행한다면 Infinite Scroll을 구현할 수 있다.
여기서 callback 부분은 react-query
에서 지원하는 useInfiniteQuery
를 활용하였다.
/**
* 데이터 가져오기 Fetch
* @function useGetDataFetch
*/
export const useGetDataFetch = ({ puuid }: getMatchsPayload) => {
const { data, fetchNextPage } = useInfiniteQuery(
['getData'],
async ({ pageParam = 1 }) => {
const result = await getDataApi({ page: pageParam });
return result.data;
},
{
getNextPageParam: (lastPage, allPages) => {
const next = lastPage?.page?.next;
if (next) {
return next;
}
}
})
return {
data,
fetchNextPage
}
}
react-query
의 useInfiniteQuery
를 활용하여 데이터를 가져오는 Custom Hook이다. fetchNextPage
를 호출하면 getNextPageParam
에서 반환한 값을 통하여 콜백함수로 넘긴 api호출 부분을 실행한다.
const observerRef = useRef(null);
const { data, fetchNextPage } = useGetMatchsFetch({ puuid });
useInfiniteScroll({ ref: observerRef, callback: fetchNextPage });
const result = data?.pages.flatMap((pageData) => pageData); // depth 제거
관찰할 엘리먼트의 ref
와 useInfiniteQuery
의 fetchNextPage
를 Custom Hook useInfiniteScroll
의 인자로 넘겨주어 Infinite Scroll을 구현한 사용부분의 예시 코드이다.