[React] useScroll 커스텀 hook

김방울·2023년 8월 2일
0

React

목록 보기
4/6
post-thumbnail

스크롤 관련해서 방향 감지나 이벤트 조작을 하는 일이 있는데, 여러 컴포넌트에서 중복으로 동일한 로직을 사용하는 게 싫어 hook으로 따로 빼보았습니다.

npm i lodash
npm i --save-dev @types/lodash // typescript 용

과도하게 이벤트가 실행되는 것을 막기 위해
lodash 라이브러리를 설치해 주었습니다. lodash에서 제공하는 debounce 메서드를 사용하면 스크롤 시 과도하게 이벤트가 발생하는 것을 방지할 수 있습니다.

// useScroll.ts
import { debounce } from 'lodash';
import { useState, useEffect } from 'react';

const useScroll = () => {
  const [scroll, setScroll] = useState<number>(0);
  const [prevScroll, setPrevScroll] = useState<number>(0);
  const [scrollDir, setScrollDir] = useState<'up' | 'down' | 'top'>('top');

  // FUNCTION 현재 스크롤 저장
  const onScrollDoc = () => {
    setScroll(document.scrollingElement?.scrollTop || 0);
  };

  //  FUNCTION 스크롤 감지
  useEffect(() => {
    if (!document) return;
    document.addEventListener('scroll', debounce(onScrollDoc, 300));

    return () => document.removeEventListener('scroll', debounce(onScrollDoc, 300));
  }, []);

  // FUNCTION 스크롤 방향 설정
  useEffect(() => {
    if (scroll === 0) {
      // 스크롤이 최상단에 있을 때를 감지하는 것이 필요하여 top 상태를 만들었는데, 최상단만 감지하는 로직으로 따로 빼면 더 좋았을 것 같습니다..!
      setScrollDir('top');
    } else if (prevScroll < scroll) {
      setScrollDir('down');
    } else if (prevScroll > scroll) {
      setScrollDir('up');
    }
    setPrevScroll(scroll);
  }, [scroll, prevScroll]);

  return { scroll, scrollDir };
};

export default useScroll;

현재 스크롤 위치인 scroll 과 방향을 감지하는 scrollDir 을 반환하는 hook을 만들었습니다.

  const scroll = useScroll();

  useEffect(() => {
    // 스크롤 위치가 달라졌을 때 이벤트 실행
    console.log(scroll.scroll);
  }, [scroll.scroll]);

  useEffect(() => {
    // 스크롤 방향이 변경되었을 때 이벤트 실행
    console.log(scroll.scrollDir);
  }, [scroll.scrollDir]);

hook 사용은 위처럼 사용할 수 있으며, 여러 컴포넌트에서 재활용 가능합니다.👾

// Header.tsx
// hook 사용 예제 전체 코드
import { useState, useEffect } from 'react';
import Logo from 'components/common/logo/Logo';
import Nav from 'components/header/Nav';
import useScroll from 'hooks/useScroll';
import { ReactComponent as NavIcon } from 'assets/image/common/nav-icon.svg';

const Header = () => {
  const [isNavOpen, setIsNavOpen] = useState<boolean>(false);
  const scroll = useScroll();

  useEffect(() => {
    console.log(scroll.scroll);
  }, [scroll.scroll]);

  useEffect(() => {
    console.log(scroll.scrollDir);
  }, [scroll.scrollDir]);

  const onClickNavBtn = () => {
    setIsNavOpen(true);
  };

  return (
    <header className={`Header ${scroll.scrollDir}`}>
      <div className='Header__inner'>
        <Logo fill={scroll.scrollDir === 'up' ? '#000' : '#fff'} />
        <button className='Header__btn--nav' onClick={onClickNavBtn}>
          <NavIcon fill='#fff' />
        </button>
        <a href='#' className='Header__btn--contact'>
          contact
        </a>
      </div>
      <Nav open={isNavOpen} setOpen={setIsNavOpen} />
    </header>
  );
};

export default Header;
profile
코딩하는 고양이🐱 / UI Developer, Front-end Developer

1개의 댓글

comment-user-thumbnail
2023년 8월 2일

정보 감사합니다.

답글 달기

관련 채용 정보