React - infiniteScroll hook 만들기
Tags: react, javascript
Published: 2021-06-01 14:50:50

> 배너 이미지 출처: https://commons.wikimedia.org/wiki/File:React_Native_Logo.png

&nbsp;  


이번 포스트에서는 React를 사용하여 "페이지 최하단 스크롤 시" 전달 받은 콜백을 실행시키는 `useInfiniteScroll` hook을 만들어 볼 것입니다.

예를 들어, 많은 양의 목록형 아이템을 페이지에 표시할 때, 최초 n개의 아이템을 먼저 표시한 후 "페이지 최하단 스크롤 시" n개의 아이템을 추가로 표시해야 하는 경우  `useInfiniteScroll` hook을 사용 할 수 있습니다. 그 외 "페이지 최하단 스크롤 시" 어떠한 로직을 수행해야할 경우에도 사용 할 수 있습니다.

바쁘신 분들은 [샌드박스 링크](https://codesandbox.io/s/use-infinite-scroll-zi4o5)에 방문하여 소스코드를 먼저 살펴봐주세요.

&nbsp;  

## useInfiniteScroll

우선 `useInfiniteScroll.js` 파일 생성 후 아래 코드를 작성합니다.

```typescript
import { useEffect } from "react";

function useInfiniteScroll(callback) {
  useEffect(
    () => {
      function scrollBottom() {
        // ???
      }
			
      // 2. 이벤트 핸들러 등록
      window.addEventListener("scroll", scrollBottom);
			
      // 3. 클린업 함수 작성
      return () => {
        window.removeEventListener("scroll", scrollBottom);
      };
    },
    // 1. 디펜던시 추가
    [callback]
  );
}

export default useInfiniteScroll;
```

1. `useEffect` hook의 디펜던시에 전달 받은 `callback`을 추가합니다. 즉, `callback`의 참조가 바뀌면 `useEffect` hook이 다시 실행될 수 있도록 합니다.
2. `addEventListener` 메소드를 사용하여 `window` 객체에 scroll 이벤트 핸들러(`scrollBottom`)를 등록합니다.
3. `callback`의 참조가 바뀔 때 실행될 클린업 함수를 작성합니다.

&nbsp;  

다음은 `scrollBottom` 함수에서 `callback`을 실행시키는 조건을 작성해보도록 합시다. 우선 "페이지 최하단 스크롤 시"라는 조건을 추가해야하는데, 먼저 아래 코드를 확인해주세요.

```react
import { useEffect } from "react";

function useInfiniteScroll(callback) {
  useEffect(() => {
    function scrollBottom() {
      const {
        scrollTop,
        clientHeight,
        scrollHeight,
      } = document.documentElement;

      // 페이지 최하단 스크롤 시
      if (scrollTop + clientHeight >= scrollHeight) {
        // callback을 실행
        callback();
      }
    }

    window.addEventListener("scroll", scrollBottom);

    return () => {
      window.removeEventListener("scroll", scrollBottom);
    };
  }, [callback]);
}

export default useInfiniteScroll;
```

위 코드를 보면 "페이지 최하단 스크롤 시"라는 조건을 `scrollTop + clientHeight >= scrollHeight`으로 판별하는 데, 해당 조건에서 사용하는 각 값에 대해서 알아볼 필요가 있습니다.

먼저 `document.documentElement`는 HTML 문서의 root element(`<html>` 요소)를 의미합니다. 즉, 위 예제 코드에서 사용된 `scrollTop`, `clientHeight`, `scrollHeight`은 각각 `<html>` 요소의 프로퍼티입니다.

`Element.scrollTop` 프로퍼티는 접근자 프로퍼티(accessor property)로서 참조 시 어떠한 값을 반환하는데, 이 값이 의미하는 것은 "해당 요소가 수직으로 스크롤 된 픽셀 값"입니다. 즉, **위 예제의 경우 `<html>` 요소가 수직으로 스크롤된 픽셀 값을 의미합니다.**

&nbsp;  

> **Element.scrollTop**
>
> The `Element.scrollTop` property gets or sets the number of pixels that an element's content is scrolled vertically.
>
> 출처: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop

&nbsp;  

`Element.clientHeight` 프로퍼티는 읽기 전용 프로퍼티로서, 요소의 내부 높이(height + padding - height of horizontal scrollbar)를 나타냅니다. **단, `<html>` 요소의 `clientHeight` 프로퍼티는 뷰포트의 높이를 나타냅니다.**

&nbsp;  

> **Element.clientHeight**
>
> When `clientHeight` is used on the root element (the `<html>` element), (or on `<body>` if the document is in quirks mode), the viewport's height (excluding any scrollbar) is returned.
>
> 출처: https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight

&nbsp;  

마지막으로 `Element.scrollHeight` 프로퍼티는 읽기 전용 프로퍼티로서 요소의 높이를 나타내는데, 이때의 높이는 **이미 스크롤되어 화면에 보이지 않는 영역까지 포함하여 계산된 값입니다.**

&nbsp;  

> **Element.scrollHeight**
>
> The **`Element.scrollHeight`** read-only property is a measurement of the height of an element's content, including content not visible on the screen due to overflow.
>
> 출처: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight

&nbsp;  

`scrollBottom` 핸들러에서 사용한 조건을 그림으로 표시하면 아래와 같습니다.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvmwnjtvk3g1zbemerrt.png)

&nbsp;  

즉, `useInfiniteScroll` hook은 **이미 스크롤된 높이(`scrollTop`) + 뷰포트 높이(`clientHeight`)의 값이 스크롤 될 수 있는 높이(`scrollHeight`)보다 크다면** 전달 받은 `callback`을 실행하는 hook입니다.

이번 포스트는 여기서 마치도록 하겠습니다.

&nbsp;