[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - Infinite Scroll

강경서·2023년 8월 10일
0
post-thumbnail

🚀 Infinite Scroll

많은 양의 컨텐츠를 사용자에게 보여주는 방법은 다양할것입니다. 다만 해당 데이터를 한번에 불러오게 되면 서버의 과부하 문제가 생길 수 있으므로 웹페이지는 나눠진 테이터를 필요한 만큼 받는 방법을 많이 사용합니다. 이러한 방식으로 데이터를 불러오면 웹페이지는 사용자에게 페이지를 클릭하면 다음 페이지 주소로 이동하는 페이지네이션, 또는 한 페이지에서 스크롤만으로 새로운 콘텐츠를 보여주는 Infinite Scroll방식을 사용합니다.


Infinite Scroll 구현하는 방식

Scroll Event

 window.addEventListener('scroll', (event) => {})

가장 간단한 방법인 스크롤 이벤트(Scroll Event)를 이용하여 무한 스크롤을 구현할 수 있습니다. 하지만 해당 방식은 Scroll Event가 과다하게 발생한다는 것입니다.

이때 적용할 수 있는 방안으로 Debounce(디바운스)와 Throttle(스로틀)이 있습니다. Debounce는 이벤트를 그룹화하여 특정 시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술이고 Throttle은 일정 주기마다 이벤트를 모아서 한 번씩 이벤트가 발생하도록 하는 기술입니다.

Debounce의 경우 특정 시간 안에 이벤트가 계속 발생하면 타임아웃이 계속 갱신되므로 이벤트 발생이 무한히 지연될 수 있지만 Throttle은 일정 주기마다 이벤트 발생을 보장하므로 무한 스크롤에는 Throttle을 사용하는 것이 더 적절합니다.


Intersection Observer API

new IntersectionObserver(callback[, options]);

Scroll Event로 무한 스크롤을 구현하면 리플로우에 의해 좋지 않은 렌더링 성능과 상황에 따라 기대한 대로 동작하지 않을 수 있는 문제점이 있습니다. 최근에 이를 해결하기 위해 주로 사용하는 것이 바로 Intersection Observer API입니다.

Intersection Observer API는 기본적으로 브라우저 Viewport와 Target으로 설정한 요소의 교차점을 관찰하여 그 Target이 Viewport에 포함되는지 구별하는 기능을 제공합니다. 이를 통해 성능적으로 더 나은 무한 스크롤을 구현할 수 있게 되었습니다.


Intersection Observer API 적용하기

import { useCallback, useEffect, useRef } from "react";

// useIntersect는 observer가 콜백시 실행시킬 함수(onintersect)를 parameter로 받습니다.
const useIntersect = (
  onintersect: (
    entry: IntersectionObserverEntry,
    observer: IntersectionObserver
  ) => void
) => {
  const ref = useRef<HTMLDivElement>(null);
  // 콜백시 entry.isIntersecting이 true일 시 onintersect를 실행합니다.
  const callback = useCallback(
    (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) onintersect(entry, observer);
      });
    },
    [onintersect]
  );
  // ref.current가 존재할 시 IntersectionObserver를 생성합니다.
  useEffect(() => {
    if (!ref.current) {
      return;
    }
    const observer = new IntersectionObserver(callback);
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, [ref.current, callback]);

  return ref;
};

export default useIntersect;

먼저 target 요소를 저장하기 위한 ref를 선언하고 root와 target이 교차 상태인지 확인하는 isIntersecting 값이 true이면 콜백을 실행하는 함수를 useCallback으로 선언합니다. 그리고 useEffect 콜백에서 IntersectionObserver 객체를 생성하고 observe 호출을 통해 target 요소의 관찰을 시작합니다. 컴포넌트가 언마운트될 때는 cleanup을 통해 disconnect를 호출하여 모든 요소의 관찰을 중지하도록 합니다.

useCallback 은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용합니다.


const PersonalListPage = () => {
  const [slice, setSlice] = useState(0);
  // exData는 목업 데이터 입니다.
  const [visibleData, setVisibleData] = useState<Data[]>(
    exData.datas.slice(0, 10)
  );
  const { data } = usePersonalReportList();
  // onintersect함수는 observer을 unobserver하고 slice를 값을 증가 시킵니다. maxSlice에 도달하면 더 이상 증가하지 않습니다.
  const onintersect = (
    entry: IntersectionObserverEntry,
    observer: IntersectionObserver
  ) => {
    const maxSlice = Math.ceil(exData.datas.length / 10) - 1;
    observer.unobserve(entry.target);
    if (slice !== maxSlice) {
      setSlice((pre) => pre + 1);
    }
  };
  // slice가 변경될떄 마다 visibleData에 데이터가 중가합니다.
  useMemo(() => {
    if (slice === 0) {
      return;
    }
    setVisibleData((pre) => [
      ...pre,
      ...exData.datas.slice(slice * 10, (slice + 1) * 10),
    ]);
  }, [slice]);

  const ref = useIntersect(onintersect);
  // ...

위에서 만든 useIntersect 커스텀 훅을 이용하여 root와 target이 교차 상태일때 발생하는 onintersect함수를 전달하여 target 요소를 저장하기 위한 ref를 받아옵니다. 그리곤 리스트의 가장 마지막에 있는 요소를 target ref를 할당하여 리스트가 끝났을때 Viewport와 target이 교차하면서 onintersect함수를 실행하게 만듭니다.


결과


📝 후기

Intersection Observer API를 사용하여 무한 스크롤을 구현해 보았습니다. 이러한 기능은 실제 프로젝트에서도 많이 사용하는 기능이라 이번 프로젝트에서도 꼭 구현해보고 싶었던 방법이었습니다. 다만 현재 서버로부터 받을 수 있는 데이터가 해당 방식에는 적합하지 않았지만 개념과 원리를 이해하면서 팀원들과 코드를 작성하는 기회가 되어서 좋았습니다.


🧾 Reference



본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

#프로젝트캠프 #프로젝트캠프후기 #유데미 #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #리액트 #react #부트캠프 #리액트캠프

profile
기록하고 배우고 시도하고

0개의 댓글