React

react-query(Infiniti Queries)와 IntersectionObserver로 무한스크롤 구현하기

FE 2022. 6. 28. 19:46

 

오늘은 react-query에 있는 infinitiQueries와 IntersectionObserverAPI로 무한 스크롤로 작업을 하였는데요. 기존에는 window 객체에 scroll 이벤트로 무한스크롤을 구현했다가 사용자 입장에서 좀 더 원활한 스크롤 이벤트를 할 수 있게끔 개선작업을 진행하였습니다.

 

여기서 기존 scroll과 intersectionOnbserverAPI를 간단하게 개념정도만 짚고 넘어가봅시다.

 

기존 scroll 이벤트는 스크롤 발생 시 많은 이벤트가 동기적으로 실행될 수 있으며, 무한 스크롤의 경우에는 scroll 이벤트가 계속 발생되기에 정말 많은 콜백들이 실행될 수 있습니다. 이는 성능에 악영향을 끼치고 큰 부하를 줄 수 있게 됩니다.

 

이러한 부분을 개선한 API인 IntersectionObserverAPI는 MDN에도 소개되었듯이 타겟 요소와 상위 요소 viewport 사이 intersection내의 변화를 비동기적으로 관찰하는 녀석입니다. 스크롤 시에 지연로딩, infiniti-scroll 등 여러모로 scroll 이벤트보다 성능면에서 이점이 있기 때문에 이 API를 이용해 개선을 시도하였습니다.

 

IntersectionObserverAPI의 첫 소개

https://developer.chrome.com/blog/intersectionobserver/

 

IntersectionObserver’s coming into view - Chrome Developers

IntersectionObservers let you know when an observed element enters or exits the browser’s viewport.

developer.chrome.com

https://react-query.tanstack.com/guides/infinite-queries

 

Infinite Queries

Subscribe to Bytes Your weekly dose of JavaScript news. Delivered every Monday to over 80,000 devs, for free.

react-query.tanstack.com

https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API

 

Intersection Observer API - Web API | MDN

Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다.Intersection Observer API는 타겟 요소와 상위 요소 또는

developer.mozilla.org

먼저 기존 scroll 이벤트가 어떻게 구현되어 있는지 봅시다.

  const {
    projects,
    fetchNextProjectList,
    hasNextPage,
    refetch,
    isFetching,
    totalElements,
  } = useInfiniteProjectList(tab, keyword);
  
  const scrollHandler = () => {
    if (window.innerHeight + Math.ceil(window.pageYOffset + 1) > document.body.offsetHeight) {
      fetchNextProjectList();
    }
  };

  useEffect(() => {
    window.addEventListener("scroll", scrollHandler);
    return () => {
      window.removeEventListener("scroll", scrollHandler);
    };
  }, []);

이렇게 구현되어 있었는데요. 위 코드로 무한스크롤을 구현하게 되면 스크롤이 가장 밑에 지점까지 가야 다음 리스트들을 불러옵니다. 여기서 가장 밑에 지점까지 가야 다음 리스트를 불러온다는 부분이 문제인데요. 제가 사용자 입장으로 어떤 사이트의 무한 스크롤이 있는 페이지를 이용했을 때 일정 지점이 되면 알아서 데이터를 오겠지? 라고 당연히 생각하게 되더라고요.

 

그러나 저희 사이트는 스크롤이 거의 99%까지 내려왔는데도 다음 데이터를 불러오지 않고 있습니다. 타 사이트를 이용할 땐 당연히 그렇겠지라고 생각하면서 제가 개발하고 있던 사이트는 이런 부분을 생각하지 못하고 있던게 순간적으로 너무 모순된 생각처럼 느껴지더라고요. 

 

그럼 어떻게 개선했는지 코드를 봅시다.

const element = useRef<HTMLElement>();

const {
    projects,
    fetchNextProjectList,
    hasNextPage,
    refetch,
    isFetching,
    totalElements,
  } = useInfiniteProjectList(tab, keyword);

  const onIntersect: IntersectionObserverCallback = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting && hasNextPage) {
        // 새로운 요청을 보내게됨 
        fetchNextProjectList();
        // 현재 타겟을 unobserve한다.
        observer.unobserve(entry.target);
      }
    });
  };

  useEffect(() => {
    //observer 인스턴스를 생성한 후 구독
    let observer: IntersectionObserver;
    if (element.current) {
      observer = new IntersectionObserver(onIntersect, { threshold: 0.8 });
      // observer할 target 요소 관찰 시작
      observer.observe(element.current);
    }
    return () => observer && observer.disconnect();
  }, [projects]);

 

IntersectionObserverAPI

  1. Intersection Observer 인스턴스를 생성 (new 키워드롤 통해 생성하며 callback, options 2개의 파라미터를 받습니다.)
  2. callback 함수와 options를 넣어 observer를 이용해 요소를 관찰 시작
  3. onIntersect라는 콜백함수를 이용해 새로운 데이터 호출
  4. 현재 타겟을 unobserve하여 타겟 요소에 대한 관찰을 중지

여기서 몇 가지 살펴보자면 options는 root, rootMargin, threshold 등의 옵션이 있는데 threshold는 어느정도 타겟 요소가 보여졌는지에 따라 콜백을 호출할 수 있게 해주는 녀석입니다. 저는 80%가 보여졌을 때 콜백함수를 실행하려고 0.8을 지정하였습니다.

 

그리고 onIntersect에 entries는 IntersectionObserverEntry 인스턴스를 담은 배열인데 이 인터페이스는 특정 전환 순간에 대상 요소와 루트 요소 간의 교차를 묘사합니다. 여기서 isIntersecting는 해당 entry에 타겟 요소가 루트 요소와 교차하는지 여부를 boolean (true, false)값으로 반환해줍니다. 그래서 isIntersecting이 true일 때 다음 데이터 요청을 보내도록 했습니다.

 

hasNextPage는 react-query에 있는 infinitiQueries의 옵션인데요. 현재 페이지에서 +1을 한 값이 전체페이지보다 크면 false 그렇지 않으면 true를 반환해주는 옵션입니다. 즉, 더 로드할 페이지의 대한 여부를 boolean값으로 받을 수 있습니다. 이 옵션 설정을 안하게 되면 데이터를 다 불러왔는데도 isIntersecting이 true일 때, 계속해서 요청을 하더라고요. 그래서 같이 설정을 해주었습니다.

 

이렇게 해서 무한스크롤을 IntersectionObserverAPI로 구현해봤는데요. 사용자 중점적 개선의 대해 좀 더 생각해볼 수 있는 계기가 된 것 같습니다. 앞으로도 타 사이트를 이용할 때 느꼈던 편리함이나 사용성의 대한 측면을 더 생각해보고 느낀점은 저희 사이트에도 반영할 수 있는 부분은 반영할 수 있게끔 반대로 다른 개발자가 저희 사이트를 이용했을 때 인사이트를 얻어갈 수 있게끔 더 노력해야겠습니다.