[React] IntersectionObserver로 무한 스크롤 구현하기

배성규·2023년 7월 12일
0

프로젝트

목록 보기
3/10

0. Intro

최근 진행하고 있는 프로젝트에서 무한 스크롤 기능을 구현해야하는 부분이 있었는데, 해당 기능을 구현해본 적이 없어 이를 구현해보고 싶었고 구현을 위해 IntersectionObserver에 대해 공부하고 프로젝트에 적용하기로 했다.

1. IntersectionObserver가 뭐지?

  • Intersection Observer API는 상위 요소 또는 최상위 문서의 뷰포트와 대상 요소의 교차에서 변경 사항을 비동기 식으로 관찰하는 것이다.
  • 페이지가 스크롤될 때 이미지 또는 기타 컨텐츠가 지연 로드되며, 사용자가 페이지를 넘길 필요 없이 스크롤만으로 컨텐츠를 로드(무한스크롤)할 수 있다.

1-1. Intersection observer의 컨셉과 사용법

  • 다음 상황 중 하나가 발생할 시 호출되는 콜백을 구성할 수 있다.
    • 대상 요소는 장치의 뷰포트 또는 지정된 요소와 교차한다. 지정된 요소는 Intersection observer API의 목적을 위해 루트 요소 또는 루트라고 부른다.
    • 관찰자가 처음에 대상 요소를 보도록 요청받은 것이다.

1-2. 생성

const io = new IntersectionObserver(callback, options) // 관찰자 초기화 
io.observe(el); // 관찰할 대상(요소)등록 
// 프로젝트에서는 다음과 같이 활용
const observer = new IntersectionObserver(code...)

if(ref.currnet) observer.observe(ref.current);

1-3. 콜백

  • 관찰할 타겟이 등록되거나 가시성에 변화가 생기면 관찰자는 콜백을 실행
  • 콜백 인수로는 entries, observer를 갖는다.
const io = new IntersectionObserver((entries, observer) => {}, options) 
io.observe(el);

1-4. entries

  • entries는 IntersectionObservserEntry 인스턴스 배열이다.
  • 읽기 전용의 다음 속성을 포함한다.
    • boundingClientRect : 관찰 대상의 사각형 정보 (전체 사각형의 정보)
    • intersectionRect : 관찰 대상의 교차한 영역 정보 (화면에 보여지는 사각형의 정보)
    • intersectionRatio : 관찰 대상의 교차한 영역 백분율
    • isIntersection : 관찰 대상의 교차 상태(Boolean값) (루트의 전체 구역)
    • rootBounds : 지정한 루트 요소의 사각형 정보
    • target : 관찰 대상 요소
    • time : 변경이 발생한 시간 정보
const io = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    console.log(entry) // entry is `IntersectionObserverEntry
  })
}, options)

io.observe(el);
// 프로젝트 적용 방법 
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          if (!checkLast) {
            getBoards(page);
          }
        }
      },
      { threshold: 0.5 }
    );

    if (ref.current) {
      observer.observe(ref.current);
    }
    return () => {
      if (ref.current) {
        observer.unobserve(ref.current);
      }
    };
  }, [page]);

1-5. 옵션

  1. root
  • 타겟의 가시성을 검사하기 위해 뷰포트 대신 사용할 요소 객체를 지정.
  • 지정하지 않거나 null일 경우 브라우저 뷰포트가 기본 사용. 기본 값은 null이며 타겟은 조상 요소여야 한다.
  1. rootMargin
  • margin을 이용하여 루트 범위를 확장 혹은 축소할 수 있다.
  • px 또는 %로 나타낼 수 있으며 사용 시에는 단위를 꼭 입력해야 한다.
  1. threshold
  • 옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시
  • 만약 0.5라면 타겟이 50%보이면 옵저버 실행, 1이라면 전체가 다 보여야 실행된다.

1-6. 메소드

  1. observe() : 대상 요소의 관찰을 시작
const div = document.querySelector('div');
observer.observe(div); // div 요소 관찰 
  1. unobserve() : 대상 요소의 관찰을 중지한다.
  • 단, 관찰을 중지할 하나의 대상 요소를 인수로 지정해야하며, 관찰 중이 아닌 요소가 지정된 경우에는 아무 동작도 하지 않는다.
observe.observe(div);
observe.unobserve(div);
  1. disconnet() : 모든 요소의 관찰을 중지
observe.observe(div);
observe.disconnect(); // observe가 관찰하는 모든 요소의 관찰을 중지 

2. 내가 구현한 방법

  • useRef()를 사용하여 DOM을 선택하고 이를 활용하는 방식으로 구현했다.
  • 위 방식을 사용하여 다시 렌더링이 되어도 기존에 참조하고 있는 컴포넌트 함수 값을 보존할 수 있었다.
import {useEffect, useState, useRef} from 'react';

export function AllPost() {
   const ref = useRef(null);

   useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          if (!checkLast) {
            getBoards(page);
          }
        }
      },
      { threshold: 0.5 }
    );

    if (ref.current) {
      observer.observe(ref.current);
    }
    return () => {
      if (ref.current) {
        observer.unobserve(ref.current);
      }
    };
  }, [page]);
  //code.. 
  
  return(
    <>
    //code..
    {post ? post.map((item) => {
    	return (
          <img src = {item.imageUrl}
               key = {item.id} 
   			   onClick = {() => routePost(item.id)} />
          );
		}) : null}
    	<div ref = {ref} />
    </>
}

3. 마무리

해당 코드를 사용하여 위와 같이 구현할 수 있었다. 구현 도중 브레이크 포인트를 걸지 않아 API를 무한 호출했었는데, 마지막 페이지임을 확인하는 isLast를 api 조건문으로 체크하여 무한 호출하는 부분을 해결할 수 있었다.

profile
FE 유망주🧑‍💻

0개의 댓글