Library:

Intersection Observer API로 무한 스크롤 구현하기

oni(오니)

intro 팀 프로젝트에서 무한 스크롤 기능을 구현하게 되었다. 처음 사용하다 보니 걱정도 많았고, 실수도 많았지만 차근차근 사용 경험을 정리해 보고자 한다. 서론에서는 많은 도움을 준 블로깅들을 간략하게 정리한 후에 본론으로 들어가 보도록 하겠다.

 

무한 스크롤(Infinite Scroll)이란? 

사용자가 특정 페이지 하단에 도달했을 때, API가 데이터를 호출하여 콘텐츠가 끊기지 않고 계속 로드되는 사용자 경험 방식이다.

한 페이지에서 스크롤만으로 새로운 콘텐츠를 보여주게 되므로, 많은 양의 콘텐츠를 스크롤하여 볼 수 있는 장점이 있다.

 

Intersection Observer를 선택한 이유

스크롤 이벤트로 무한 스크롤을 구현하면 각 Element마다 이벤트가 등록되어 있어 감지될 때마다 이벤트가 끊임없이 호출된다는 문제점이 있었고 이는 곧 리플로우에 좋지 않다는 글들을 많이 접할 수 있었다. 많은 블로깅 글과 멘토님의 코멘트 덕분에 해당 API로 기능을 구현하기로 결정하였다.

 

 

Intersection Observer란?

내가 사용한 건 Intersection Observer API이다.

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

아래는 MDN에서 정의한 내용을 가져와봤다.

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

 

# root

: 이 옵션에 정의된 Element를 기준으로 Target Element가 노출 여부를 판단한다. null 또는 지정되지 않았을 때 기본값으로 Browser Viewport가 설정된다.

# rootMargin

: root에 정의된 Element가 가진 마진 값을 의미한다. 사용법은 CSS의 margin 속성과 매우 유사하다. threshold를 계산할 때 rootMargin 만큼 더 계산한다. 기본값은 0px 0px 0px 0px이다. (반드시 단위 입력 필요)

# threshold

: Target Element가 root에 정의된 Element에 노출되었을 때 Callback 함수를 실행시킬지 정의하는 옵션이다. 콜백이 실행되기 위해 target의 가시성이 얼마나 필요한지 백분율로 표시한다. 기본값은 배열 [0]이며, Number 타입의 단일 값으로 작성 가능하다. number로 정의할 경우, Target Element의 노출 비율에 따라 Callback 함수를 한번 호출할 수 있지만, number[ ]로 정의할 경우, 각각의 비율로 노출될 때마다 Callback 함수를 호출한다.

let options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);

➡ 즉, Intersection Observer API는 Target 요소가 root 요소와 교차가 일어나는지를 판단하여 콜백 함수를 실행한다!

 


Intersection Observer 실 사용기 (in React)

빠른 적용을 위해 Intersection Observer를 더 쉽게 사용할 수 있는 라이브러리를 통해 기능을 구현하였다.

설치하기

npm install react-intersection-observer

 

1. target 생성하기

해당 라이브러리에서는 useInView 훅을 제공하는 데 이를 사용하면 구성 요소의 상태를 쉽게 모니터링할 수 있다. 필요한 옵션을 넣어 useInView를 호출한다. 모니터링하려는 DOM 요소에 ref를 할당하면 inView가 상태를 보고한다.

  • ref가 걸어있는 태그가 화면에 보이면 inView가 true, 안 보이면 false로 자동 변경된다.
  • 마지막 요소에 ref를 걸어주고, 크기를 주었다. (width: 100%, height: 30px)

2. 콜백 함수 생성하기

데이터 요청 함수이다. page를 받아서 해당 page를 요청하게 되고, 받아온 데이터를 상태에 저장해 주고 있다.

getLetters는 data가 바뀔 때마다 재생성되는 함수이다.

💥 테스트 중 만난 오류 사항

이곳에서 렌더링이 될 때마다 같은 데이터가 중복되는 현상이 발생하였다. 초깃값을 주어도 이를 해결할 수 없었다.

이럴 때는 내가 햇병아리구나... 싶긴 하다. 결국 멘토님의 도움으로 문제를 해결하였다.

아직 설명하진 못했지만, 아래 이미지도 함께 보면서 이해를 하면 좋을 것 같다!

초창기 나의 코드에는 콜백 함수 매개변수로 아무 값도 받지 않았다. 하지만 호출 문에서 page 전달받는 것으로 변경하였고, 데이터를 상태에 저장할 때 분기 처리를 해주었다.

만약 1페이지라면 1페이지 데이터만 가져오고, 그게 아닌 그다음 페이지부터는 데이터가 쌓이도록 작성하였다.

그랬더니 간단히 문제가 해결되었다.

보고 나면 너무 당연한 코드가 내 머릿속엔 왜 없는 걸까... 흑흑 더 분발하자!!!!!

 

+ useCallback을 내가 잘 알지 못하고 사용하고 있다는 생각도 들어, useCallback에 대한 블로깅도 진행해 보도록 하겠다. 현재 코드가 조금 어색해 보여도 잠시 눈감아주자...

 

 

3. 실행하기

두 번째 useEffect부터 보자. 해당 useEffect은 inView의 상태가 변경될 때마다 실행하게 된다.

만약 사용자가 마지막 요소를 보고 있고(inView가 true이고), 로딩이 false 이면,

로딩이 true가 돼서 로딩 문구가 나타나고 1.5초 뒤에 페이지가 1 증가하여 페이지의 상태가 바뀌게 된다.

로딩은 다시 false로 지정해 준다.

페이지의 상태가 바뀌면, 첫 번째 useEffect 문이 실행되게 된다. 즉, 1이 증가된 페이지 숫자를 전달하여 데이터 요청이 보내진다. 그럼 다시 2번에서 데이터를 상태에 저장하게 되고, 화면에 map으로 데이터가 뿌려지게 된다.

요약하면 맨 하단으로 내렸을 때 page 값이 증가되면서 다음 page에 대한 데이터를 서버에서 가져온다.

 


우여곡절 끝에 무한스크롤링 구현을 성공하였다. 하지만 데이터를 저장하는 방식이나 내가 작성한 로직에 대해서 더 고민해 보고 싶다. 무한스크롤에서 필터링을 하는 방법에 대해 그리고 실제 API로 구현하는 방법도 추후 블로깅에 추가 작성하고 싶다.

 

2023.03.21. 15:02


📜 출처