Intersection Observer , useInfiniteQuery를 사용하여 손쉽게 무한 스크롤 구현하기

Hoon·2023년 7월 15일
2

React

목록 보기
2/15
post-thumbnail

흔히 Infinite Scroll을 구현하는 방법으로는 Scroll Event를 이용한 방식과 Intersection Observer를 이용한 방식으로 나뉜다.

Scroll Event로 구현한 방식은 사용자가 scroll을 할때마다 이벤트가 실행되기에 성능적으로 좋지 않다. (throttle 혹은 debounce로 최적화 필요)

이번 글에서는 이전에 프로젝트에서 사용했던 Intersection Observerreact-queryuseInfiniteQuery를 이용해 구현한 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-queryuseInfiniteQuery를 활용하여 데이터를 가져오는 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 제거

관찰할 엘리먼트의 refuseInfiniteQueryfetchNextPage를 Custom Hook useInfiniteScroll의 인자로 넘겨주어 Infinite Scroll을 구현한 사용부분의 예시 코드이다.

profile
개발자 Hoon입니다

0개의 댓글