[웹퍼즐] 메인 페이지: 무한 스크롤과 throttle

🐈 JAELEE 🐈·2021년 11월 28일
0

웹퍼즐 프로젝트

목록 보기
6/7

하옹의 프론트앤드 이야기 - Infinite Scroll(무한 스크롤)
Throttle과 Debounce에 대한 설명
react-intersection-observer로 무한스크롤 구현하기

throttle과 debounce

둘 모두 연이어 발생하는 이벤트에 의해 무의미한 리소스 낭비가 일어나지 않게끔 방지하는 기법이다.

  • throttle: 이벤트에 의한 콜백을 일정시간 뒤에 호출하는 것.
    ex) 스크롤할 경우 console.log를 일정 시간 간격마다 호출하기
var throttler;
window.onscroll = () => {
  // throttle
  if(!throttler) {
    throttler = setTimeout(() => {
      throttler = null; console.log('throttle');
    }, 200);
  }
}

출처: https://canoe726.tistory.com/22 [All-in-one]
  • debounce: 연이어 호출되는 콜백 중 마지막 or 처음 것만 호출하도록 하는 것.
    ex) input에 입력을 할 경우 입력이 끝난 시점으로 부터 500ms 이후 console.log 호출하기
var debouncer;
document.querySelector('.search').addEventListener('input', e => {
  // debounce
  if(debouncer) {
    clearTimeout(debouncer);
  }
  debouncer = setTimeout(() => {
    console.log('debounce');
  }, 500);
});

출처: https://canoe726.tistory.com/22 [All-in-one]

무한스크롤

  • 무한스크롤 구현은 크게 두 가지 방법으로 구현된다.
    1. scroll event: DOM scroll Event를 이용하는 것이라 상대적으로 구현은 쉽지만 throttle이나 rAF로 최적화를 해줘야 한다.
    2. IntersectionObserver: 마지막 요소가 사용자에게 보이면 다음 데이터를 불러오는 방법.
    • 구현 난이도: scroll event < IntersectionObserver
    • 성능: scroll event < IntersectionObserver

IntersectionObserver 기반

react-intersection-observer로 무한스크롤 구현하기

react의 react-intersection-observer 라이브러리를 사용하면 IntersectionObserver 기반의 무한 스크롤을 쉽게 구현할 수 있다.

import React from "react"
import { useInView } from "react-intersection-observer"

const App = () => {
  const [ref, inView] = useInView()

  return (
    <div ref={ref}>
      Element {inView.toString()}
    </div>
  )
}

export default App

ref 를 div에 걸어주면 해당 요소가 보이면 inView가 true로, 안 보이면 false로 자동으로 변경된다.

scrollevent 기반

라이브러리를 쓰지 않는 데에 의의를 두며 우리 프로젝트의 무산스크롤 구현 방법으로 최종적으로 scroll event 기반 방법을 선택했다. 또한 fetch를 적게 받아오기 위해 캐싱도 적용했다.

Scroll Event를 이용한 infiniteScroll Custom Hook 만들기

https://ha-young.github.io/2021/frontend/infinite-scroll/

  1. infiniteScroll Custom Hook: ref.addEventListener("scroll", scrollEvent); 의 scrollEvent에서 일정시간마다 scroll의 길이가 Math.abs(ret - scrollHeight) <= 50인지 확인하고 만약 그러하다면 isSettingtrue로 바꾼다.
  2. infiniteScroll Custom Hook: useEffect(()=>{}, \[isSetting\])에서 isSetting이 true라면 fetchCallback을 호출한다.
  3. infiniteScroll Custom Hook의 부모(page/index.tsx): 우리 프로젝트의 경우 1번의 fetchCallback 함수는 page/index.tsx의 getImgUrl이란 함수인데,
    이 함수에서 cache 변수가 undefined라면 post요청을 보내 전체 목록을 받아오고,
    cache 변수가 undefined가 아니라면, 원하는 데이터 수만큼 slice한다. 그렇게 얻은 목록은 부모 컴포넌트의 useState로 선언된 img 배열에 추가되고, 리랜더링이 된다.
  4. infiniteScroll Custom Hook의 부모(page/index.tsx): 1번의 fetch 함수가 호출되었을 때 더는 slice할 데이터가 없다면 infiniteScroll Custom Hook의 isDone을 true로 바꿔준다.
  5. infiniteScroll Custom Hook: infiniteScroll Custom Hook에선 isDone의 값이 바뀌어 리랜더링되며 scroll 이벤트 리스너를 해제한다.

이슈: removeEventListener

위의 4. 이벤트리스너 삭제에서 removeEventListener가 잘 수행되지 않는데, 이벤트 함수를 선언한 위치의 문제였다. scrollEvent를 다음과 같이 useEffect 안에 선언하니 원하는 대로 동작했다.

  useEffect(() => {
    if (!ref) return;
    prev = 0;
    const scrollEvent = () => {
      throttle(handleScroll, 100);
    };
    if (!isDone) ref.addEventListener("scroll", scrollEvent);
    return () => {
      ref.removeEventListener("scroll", scrollEvent);
    };
  }, [ref, isDone]);

0개의 댓글