[React] react + 무한 스크롤(feat. intersection Observer API)

Gyuhan Park·2023년 1월 20일
0

react

목록 보기
10/11
post-thumbnail

scroll event를 안 쓰는 이유

굳이 Intersection Observer API를 사용하여 무한 스크롤을 구현하는 이유는?

  • debounce & throttle 을 구현하지 않아도 된다.
  • scroll event를 사용해서 구현할 때 구하는 offsetTop 값을 구할 때 매번 layout을 새로 그리는데 이를 Reflow라고 하는데, Intersection Observer를 사용하면 Reflow를 하지 않는다.

Intersection Observer API

타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법(출처 : MDN)
=> 이게 무슨 말일까? 우리가 정한 target 요소와 viewport가 교차하는 부분의 변화를 관찰한다. 즉, 관찰 중인 요소가 사용자가 보는 화면에 보이는 지 확인할 때 쓰는 API

intersection 정보는 언제 쓰일까?

  • 페이지가 스크롤 되는 도중에 발생하는 이미지나 다른 컨텐츠의 지연 로딩.
  • infinite-scroll 을 구현.
  • 광고 수익을 계산하기 위한 용도로 광고의 가시성 보고.
  • 사용자에게 결과가 표시되는 여부에 따라 작업을 수행할 지 결정.

intersectionObserver 객체 생성

new IntersectionObserver(callback, options)

옵션에는 root, rootMargin, threshold 가 있는데 보통 threshold만 사용한다. threshold: 0.7이면 target이 화면에 70% 이상 보이기 시작할 때 콜백함수를 호출한다.

무한스크롤 동작 순서

  • useRef()로 target element 설정
  • Observer에서 감지된 target를 포착해 getMoreItem() 실행
  • 비동기 요청을 보낸다(나는 원래 있던 요소를 복사할 것이므로 그냥 1.5초 대기)
  • 로딩 시작 -> 데이터 추가 성공 -> 로딩 종료

observer가 관찰할 target 요소를 설정한다.

  return (
    <div>
      ...
      <div ref={setTarget}>{isLoaded && <Loading />}</div>
    </div>
  );

target이 화면에 70%이상 보이면 onIntersect 콜백함수를 실행한다.

  useEffect(() => {
    let observer;
    if (target) {
      observer = new IntersectionObserver(onIntersect, {
        threshold: 0.7,
      });
      observer.observe(target);
    }
    return () => observer && observer.disconnect();
  }, [target]);

target element가 화면에 보였을 때 새로운 데이터 추가 후 마지막 요소를 다시 관찰한다.

const [isLoaded, setIsLoaded] = useState(false);

const onIntersect = async ([entry], observer) => {
  if (entry.isIntersecting && !isLoaded) {
      observer.unobserve(entry.target); // 기존 관찰하던 요소는 관찰하지 않음
      await getMoreItem(); // 새로운 데이터 추가
      observer.observe(entry.target); // 새로운 데이터 마지막 요소 관찰
    }
  };

서버에 비동기 요청을 보내 데이터를 받아와서 state에 추가한다. 나는 임의로 갖고 있는 데이터를 복사하여 비동기 요청을 기다리는 것처럼 1.5초를 대기 후 데이터를 추가하였다.

const getMoreItem = async () => {
    setIsLoaded(true);
    await new Promise((resolve) => setTimeout(resolve, 1500));
    const items = recommendItems.map((item) => {
      return { ...item, id: nextId.current++ };
    });

    setRecommendItems((recommendItems) => recommendItems.concat(items));
    setIsLoaded(false);
  };

결과 화면

Skeleton UI

로딩중일 때 다음과 같이 보이게 될 화면의 윤곽을 미리 보여주는 애니메이션이다.
유튜브에서 자주 볼 수 있는데 스피너보다 사용자 친화적이고 이탈율을 줄일 수 있다.

react-loading-skeleton

react-loading-skeleton 라이브러리를 이용해 skeleton UI를 구현하였다.

<Skeleton />

컴포넌트로 쉽게 구현할 수 있고 필요한 요소를 라이브러리 내부의 속성들을 이용하거나 css를 적용시켜 원하는 모양을 만들 수 있다.
또한 skeleton.css 를 적용하면 로딩중일 때 애니메이션이 적용된다. 라이브러리를 자세히 알고 싶다면 아래 링크에 자세히 설명되어 있다.
react-loading-skeleton

import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import styles from "./SkeletonElement.module.scss";

const SkeletonElement = () => {
  return (
    <div>
      <div>
        <Skeleton className={styles.avatar} />
        <div className={styles.info}>
          <Skeleton width={"200px"} />
          <Skeleton width={"200px"} />
        </div>
      </div>
    </div>
  );
};

export default SkeletonElement;

결과 화면

MDN Intersection Observer
intersectionObserver 무한스크롤 구현하기
무한스크롤 구현하기
무한 스크롤 구현하는 4가지 방법

react에서 스켈레톤 컴포넌트 만들기

profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글