[React] Infinite Scroll 구현

Kimyujin·2021년 7월 27일
0
post-thumbnail

Scroll Event

제일 간단하게 할 수 있는 것은, window에게 scroll event를 거는 방법이다. 비교적 간단하게 할 수 있지만, 스로틀(throttle) 함수를 적용해주지 않으면 브라우저의 성능에 굉장히 좋지 않다. (엄청나게 많은 요청이 간다.)

getBoundingClientRect()

요소의 좌표를 얻는 getBoundingClientRect 함수로 scroll event를 구현할 수도 있다. 하지만 이 방식은 Reflow를 일으켜서 좋은 방법은 아니다.

InterSection Observer API

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

장점

성능 최적화에 좋은 API이다

  • Reflow를 하지 않는다.
  • scroll event 외에도 lazy loading과 같은 최적화 기능을 구현할 수 있다.
  • 요소를 관찰해서 기능을 구현할 수 있기 때문에, 최적화를 위해 라이브러리가 추가로 필요하지 않다.

단점

응용하기 어렵다

무한스크롤 외에도 다양하게 사용할 수 있는 API같은데, 나에게는 많이 어려웠다. 다양하게 응용해봐야 익숙해질 것 같다.

구현

마지막 요소에 닿을 때, 데이터를 요청해서 list에 push하는 형태로 구현한다.

필요한 상태들

무한 스크롤 구현을 위해 기본적으로 있어야할 것들이 있다.

function App() {
  const [page, setPage] = useState(1); // 페이지 번호를 위해 필요
  const [list, setList] = useState(null); // 데이터를 받아와서 저장
  const [element, setElement] = useState(null); // 요소의 관찰을 위해 필요
  const URL = '~~';
  

데이터 가져오는 함수

데이터를 요청하고, 렌더링 하기위한 함수를 선언한다.

  const fetchData = async (page) => {
    const result = await axios.get(
      `${URL}?_page=${page}&_limit=10`
    );
    // 데이터를 가져오고 난 뒤, 데이터를 추가한다.
    setList((prev) => {
      if (!prev) return result.data;
      else return [...prev, ...result.data];
    });
  };
 // ...
}

리스트 렌더링

map을 이용해 Comments 컴포넌트를 렌더링한다.

return (
    <Wrapper>
      {list &&
        list.map((comment, index) => {
          return (
            <div ref={setElement} key={index}>
              {/* 마지막 div가 element에 담긴다. */}
              <Comments
                id={comment.id}
                email={comment.email}
                body={comment.body}
              />
            </div>
          );
        })}
    </Wrapper>
  );

데이터 가져오기

// observer를 만들어서 계속 이용한다.
// IntersectionObserver라는 Web API를 이용

const observer = useRef(
    new IntersectionObserver(
      (entries) => {
        const first = entries[0];
        // isIntersecting의 값이 true일 때는 page 값을 더한다.
        // isIntersecting은 관찰로 인해서 true 혹은 false가 되는 값이다.
        if (first.isIntersecting) {
          setPage((prev) => prev + 1);
        }
      },
      { threshold: 1 } 
    )
  );

// page 값이 늘어날 때마다 데이터를 가져온다.
  useEffect(() => {
    fetchData(page);
  }, [page]);

element 관찰하기

  useEffect(() => {
    // element가 바뀔때마다 동작한다.
    const currentElement = element;
    const currentObserver = observer.current;

    if (currentElement) {
      currentObserver.observe(currentElement);
    }

    return () => {
      if (currentElement) {
        currentObserver.unobserve(currentElement);
      }
    };
  }, [element]);

이해가 잘 안가서 콘솔에 찍어보았다.

1) 처음 접속시 currentElement는 마지막 요소(여기서는 10번째 div)로 저장된다.
2) 스크롤을 내리다가 맨 밑 div에 닿으면 데이터를 요청한다.
3) 10번째 div는 관찰이 종료된다. (unobserve)
4) currentElement는 20번째 div가 된다.

전체코드 보러가기

0개의 댓글