탑 스크롤 만들기 (feat. 이벤트 최적화 적용 - debounce, useCallback)

yesung·2024년 1월 16일
6

홈 페이지 하단 영역이 길어지는 만큼 사용자 경험 측면을 고려해서 Top 버튼 이 필요했다.

ScrollTop

const ScrollTop = () => {
  const [showButton, setShowButton] = useState(false);

  const clickScrollTopHandler = () => {
    window.scrollTo({ top: 0, behavior: 'smooth' });
  };

  useEffect(() => {
    const clickShowButtonHandler = () => {
      if (window.scrollY > 700) {
        setShowButton(true);
      } else {
        setShowButton(false);
      }
    };

    window.addEventListener('scroll', clickShowButtonHandler);

    return () => {
      window.removeEventListener('scroll', clickShowButtonHandler);
    };
  }, []);

  return (
    <>
      {showButton && (
        <div className={styles.scrollTopBox}>
          <button onClick={clickScrollTopHandler}>
            <p>Top</p>
          </button>
        </div>
      )}
    </>
  );
};

먼저 show/hide 처리를 하기 위해 useState로 버튼 상태 관리를 진행했고 window 내장 API인 scrollTo로 클릭 핸들러를 생성해서 페이지 상단으로 부드럽게 스크롤 되도록 옵션을 부여했다.

그리고 useEffect를 사용해서 마운트 될 때 scroll 이벤트 리스너로 clickShowButtonHandler 함수를 콜백했고 언마운트 시, scroll 이벤트를 정리했다.

window.scrollY > 700 조건은 스크롤 위치가 700px 이상일 때를 의미한다.

단, 리렌더링 여부를 파악하기 위해 console.log를 출력한 결과...

상당히 마음이 불편해진 결과를 볼 수 있었다.

useCallback 적용

clickShowButtonHandler 함수가 스크롤 이벤트가 발생할 때 마다 호출되고 있어서 성능적인 측면에서 굉장히 떨어졌고 이벤트 핸들러 내에서 사용되는 조건문을 최적화 하기 위해서 useCallback 훅을 적용해서 clickScrollTopHandler와 clickShowButtonHandler 함수가 필요할 때만 재생성되도록 했다.

const ScrollTop = () => {
  const [showButton, setShowButton] = useState(false);

  const clickScrollTopHandler = useCallback(() => {
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }, []);

  const clickShowButtonHandler = useCallback(() => {
    setShowButton(window.scrollY > 700);
  }, []);

  useEffect(() => {
    window.addEventListener('scroll', clickShowButtonHandler);

    return () => {
      window.removeEventListener('scroll', clickShowButtonHandler);
    };
  }, [clickShowButtonHandler]);

  return (
    showButton && (
      <div className={styles.scrollTopBox}>
        <button onClick={clickScrollTopHandler}>Top</button>
      </div>
    )
  );
};

적용 시킨 코드 결과이다.

clickShowButtonHandler을 이벤트 핸들러 내부에서 setShowButton을 직접 호출하도록 해서 불필요한 조건문을 줄였다.

더불어 useCallback을 적용시킴으로써 위 작업한 컴포넌트가 리렌더링될 때마다 불필요한 함수 생성을 방지해서 메모리 사용량을 줄이고 성능을 향상 시킨 결과를 얻었다.

더 이상 console.log로 인한 마음이 어려워지는 현상은 가셨다.

debounce 적용

패키지의 맞게 설치하면 된다.

npm install @types/lodash
yarn add @types/lodash

debounce 함수는 연속된 호출을 그룹화하여 단 하나의 함수 호출만 실행되도록 지연시키는 기법

import { debounce } from 'lodash';

const clickShowButtonHandler = debounce(() => {
    setShowButton(window.scrollY > 700);
  }, 100);

useEffect(() => {
    ...

    return () => {
      window.removeEventListener('scroll', clickShowButtonHandler);
      clickShowButtonHandler.cancel();
    };
  }, [clickShowButtonHandler]);

굉장히 간단하게도 debounce를 import 후 호출하고 setShowButton을 감싸주면 된다.

이렇게되면 빈번하게 일어나는 이벤트의 발생을 줄여주는 효과를 볼 수 있다. 난 100밀리초로 해놔서 1초마다 지연시켜 호출시켰다.

쉽게 이해를 돕자면 1초 마다 지연 시키면서 setShowButton 함수가 호출되면서 showButton 상태를 업데이트한다. (스크롤이 멈춘 후에 함수를 실행하도록 하는 것이다.)

그리고 컴포넌트가 언마운트될 때 마다 debounce에 의해 예약된 호출을 취소하는 cancel 메서드를 호출하는 것도 중요하다.

profile
Frontend Developer

0개의 댓글