1. scroll 이벤트와 window.scrollY

window에는 ‘scroll’이라는 이벤트가 존재한다.

Screenshot 2024-05-18 at 4.23.02 PM.png

이 두가지를 활용할 것이다.

2. scroll 계산하기

로직은 다음과 같다. scrollY값을 상태값으로 저장한다. 그리고, window.addEventListenr(’scroll’)에, scrollY값을 구하여 이전에 저장한 scrollY값보다 크다면 스크롤이 내려간 것이고, 아니라면 올라간 것으로 판단한다.

const [scrollY,setScrollX] = useState(0);
useEffect(()=>{
	const handleScroll = () => {
		const currentScrollY = window.scrollY;
		if(currentScrollY>scrollY){
			// 스크롤이 내려간다 = Header를 숨긴다.
		}else{
			// 스크롤이 올라간다 = Header를 보여준다.
		}
		setScrollY(currentScrollY);
	}
	window.addEventListner('scroll',handleScroll);
	return () => window.removeEventListener('scroll',handleScroll);
},[scrollY])

3. 추상화하기

이 scroll을 계산하는 과정을 추상화해보자.

useScroll훅을 사용할 때, scrollY값은 항상 필요할테니 return하도록 한다. 그리고, scroll이 변경되었을 때 추가 동작을 위해 callbackFn을 인수로 전달받는다.

// useScroll.ts
export default function useScroll({ callbackFn }: UseScrollProps = {}) {
  const [scrollY, setScrollY] = useState(0);

  useEffect(() => {
    const handleScroll = () => {
      setScrollY(window.scrollY);
      callbackFn?.();
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [scrollY]);

  return {
    scrollY,
  };
}

// HeaderWrapper.tsx
const { scrollY: prevScrollY } = useScroll({
  callbackFn: () => {
    const currentScrollY = window.scrollY;
    setIsChildrenShow(prevScrollY > currentScrollY);
  },
});

4. TMI

현재, Header는 서버에서만 렌더링하기 위해 RSC 컴포넌트를 유지하고 있다. RSC에서는 클라이언트에서만 동작하는 리액트 훅을 사용하지 못한다. 그래서, Header외에 HeaderWrapper 컴포넌트를 만들었다. 구조는 다음과 같다.

// layout.tsx
return (
	<HeaderWrapper> // RCC
		<Header /> // RSC
		// ..
	</HeaderWrapper>
)