ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] Intersection Observer API : 무한 스크롤 구현
    React 2022. 7. 3. 17:25

     

     

    Intersection Observer API - Web API | MDN

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

    developer.mozilla.org

     

    🤔Intersection Observer API란?


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

     

    즉,  내가 지정한 요소가 화면(뷰포트) 상에 보이고 있는지를 관찰하는 API이다.

     

     

    왜 사용할까?


    기존의 scroll 같은 이벤트 기반의 요소 관찰에서 발생하는 렌더링 성능이나 이벤트 연속 호출 같은 문제 없이 사용 가능

         - scroll 이벤트는 동기적으로 실행 -> 메인 스레드에 큰 부하를 줄 수 있다.

         - getBoundingClientRect 사용시 reflow 발생  

            reflow : 엘리먼트가 문서내에서 어디에 위치하는지에 대한 정보를 계산해서 다시 렌더링을 하는 현상

    Intersection Observer API는 비동기적으로 실행하여 메인 스레드에 영향을 주지 않는다.

    IntersectionObserverEntry의 속성을 활용하면, reflow 없이 element의 위치를 감지할 수 있다.

     

    어떤 상황에 사용할까?


    • Lazy-loading
    • infinite scrolling
    • 광고 수익 계산을 위한 광고의 가시성 확인
    • 사용자가 결과를 봤는지에 따라 애니메이션 혹은 어떠한 동작을 수행할지 안할지 결정

     

     

     

     어떻게 사용할까?


     intersection observer 생성

    const io = new IntersectionObserver(callback, options);
    io.observe(element);

    new IntersectionObserver()를 통해 생성한 인스턴스(io)로 관찰자(Observer)를 초기화하고 관찰 대상(element) 지정

    Methods

    io.observe(element)  // => element 관찰 시작
    io.unobserve(element) // => element 관찰 중단
    io.disconnect() //  => 모든 element 관찰 중단

    callback

    관찰 대상이 등록되거나 가시성(Visibility, 보이는지 보이지 않는지) 변화가 생기면 콜백 실행

    콜백은 2개의 인수(entriesobserver)를 가진다.

    entries

    const io = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        entry.boundingClientRect // => element.getBoundingClientReac()
        entry.intersectionRect // => element의 감지된 부분의 사각형 정보
        entry.intersectionRatio // => element가 루트 요소와 얼마나 겹치는 지 0~1 사이
        entry.isIntersecting // => element의 교차 상태
        entry.rootBounds // => 루트 요소의 사각형 정보
        entry.target // => element
        entry.time // => element와 루트 요소의 교차 발생시간
      })
    }, options)
    
    io.observe(element)

    observer

    콜백이 실행되는 해당 인스턴스를 참조

    options

    root => 기본값 null, 관찰 대상을 검사하기 위해 뷰포트 대신 사용할 요소 지정

    rootMargin  => 기본값 0px 0px 0px 0px, root의 margin 값 설정

    threshold => 기본값 0,  콜백이 실행되기 위한 백분율 지정 0 ~ 1, 혹은 배열

     

     

    ✨리액트로 무한 스크롤 구현


     const [list, setList] = useState([]);
     const [target, setTarget] = useState(null);
     const [pageNumber, setPageNumber] = useState(0);
     
     // 초기값 렌더링, pageNumber 바뀔 때마다 호출
     useEffect(() => {
     	// ~데이터 가져오는 로직~
     	// setList((list) => list.concat(data))
      }, [pageNumber]);
     
    
     const callback = useCallback(
        (entry, observer) => {
          if (entry[0].isIntersecting) {
            setPageNumber((prev) => prev = prev + 1)
            observer.unobserve(entry[0].target);
          }
        },
        [setPageNumber]
      );
     
     useEffect(() => {
        if (!target) return;
        const io = new IntersectionObserver(callback);
        io.observe(target);
        return () => observer.disconnect();
      }, [callback, target]);
     
     
     return (
        <>
          <div>
            {list.map((item) => (
              // list item 요소 출력
            ))}
          </div>
          <div ref={setTarget}></div>
        </>
      );

     

    clean-up

     - 스크롤을 내려 새로운 목록을 받아오게 되므로 관찰할 대상이 바뀌기 때문에 disconnect()로 관찰요소를 없애고 새로 지정

    useCallback

     - 재렌더링 시 함수가 새로 만들어지지 않는다. 함수 안에서 사용하는 state 또는 props가 있다면 deps 배열에 포함

     

    custom hook으로 만들기


    // useIntersectionObserver.jsx
    
    import { useEffect, useState } from "react";
    
    const useIntersectionObserver = ({ callback }) => {
      const [target, setTarget] = useState(null);
    
      useEffect(() => {
        if (!target) return;
        const observer = new IntersectionObserver(callback);
        observer.observe(target);
        return () => observer.unobserve(target);
      }, [onIntersect, target]);
    
      return { setTarget };
    };
    
    export default useIntersectionObserver;
    // 사용할 곳
    
    const callback = useCallback((entry, observer) => {생략}, [])
     
     const { setTarget } = useIntersectionObserver({ callback });
     
     return (
        <>
          // 생략
          <div ref={setTarget}></div>
        </>
      );

     

     

    ✍️개인적인 정리


     

    + 렌더링이 두 번 발생


    구현하다가 처음에 데이터 호출이 두 번씩 돼서 왜 그런가 했더니 React.StrictMode가 적용되어서 그랬다.

    creat-react-app으로 생성하면 잠재적인 문제를 알아내기 위한 도구로 StrictMode가 기본으로 적용된다.

    개발 모드에서만 활성화되기 때문에, 프로덕션 빌드에는 영향을 끼치지 않는다고 한다. 지워주면 한 번만 호출이 된다.

    ReactDOM.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>,
        document.getElementById("root")
    );

     

     

     

    반응형

    댓글

Designed by Tistory.