let beforeScrollY = 0; //이전 스크롤 초기값
let menu = document.getElementsByClassName("main__first");
useEffect(() => {
window.addEventListener("scroll", scrollDirection);
if (token) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
}, []);
const scrollDirection = () => {
//이전 스크롤된 양과 현재 스크롤된 양 비교하여 방향 감지
if (document.documentElement.scrollTop > beforeScrollY) {
//아래방향
menu[0].classList.add("hidden");
} else {
menu[0].classList.remove("hidden"); //위방향
}
beforeScrollY = document.documentElement.scrollTop; //직전 스크롤양 저장
}
return(
<div className="main__first">
위와 같이 작성하게 되면 DOM 요소에 직접적으로 액세스해 클래스를 추가하기 때문에 코드가 커지고 복잡해지게 되면 DOM과 React의 상태를 구분하기 어렵고 테스트, 디버그하기 어려워져 좋은 코드가 될 수 없다.
const [isNavOn, setIsNavOn] = useState(true);
let beforeScrollY = 0; //이전 스크롤 초기값
useEffect(() => {
window.addEventListener("scroll", scrollDirection);
if (token) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
}, []);
const scrollDirection = () => {
if (window.pageYOffset > beforeScrollY) {
setIsNavOn(false);
console.log("켜기");
} else {
setIsNavOn(true);
console.log("끄기");
}
//이전 스크롤값 저장
beforeScrollY = window.pageYOffset;
}
return(
<div className={isNavOn ? "main__first" : "main__first hidden"}>
정상적으로 작동하긴 하지만 스크롤을 살짝만 움직여도 콘솔에 찍히게 둔 스크롤 이벤트가 엄청나게 찍히는걸 볼 수 있었다. 수정한 코드도 비효율적인 코드라고 결론을 내렸고 이벤트를 줄일 수 있는 방법을 검색해보다 throttle을 이용하면 해결이 가능하다는 글을 보았다.
throttle은 함수가 한 번 호출되면 지정된 시간 안에 여러번 실행되지 않도록 막아주는 것이다. 함수 이름 수정과 함께 코드를 수정해 보았다.
const [isNavOn, setIsNavOn] = useState(true);
//이전 스크롤 초기값
let beforeScrollY = 0;
useEffect(() => {
window.addEventListener("scroll", scrollEvent);
if (token) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
}, [isLoggedIn, setIsLoggedIn, token]);
const scrollEvent = useMemo(
() =>
throttle(() => {
if (window.pageYOffset > beforeScrollY) {
setIsNavOn(false);
console.log("켜기");
} else {
setIsNavOn(true);
console.log("끄기");
}
//이전 스크롤값 저장
beforeScrollY = window.pageYOffset;
}, 300),
[isNavOn]
);
return(
<div className={isNavOn ? "main__first" : "main__first hidden"}>
useMemo를 이용해 scrollEvent 함수를 메모이제이션 하고 원하는 throttle 함수 호출 시간을 설정해 준다. 그리고 useState로 선언한 변수를 의존 배열에 넣어준 뒤, 이벤트리스너의 콜백 함수로 넣어주었다. 콘솔에 찍히는 이벤트가 확연히 줄었음을 확인할 수 있다.
Assignments to the 'beforeScrollY' variable from inside React Hook useMemo will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useMemo
검색해보니 useMemo 안에 들어가는 변수를 let이 아닌 다른 키워드로 선언을 해주어야 하는 것 같았다. 이 게시물에서는 해결법으로 let이 아닌 useState로 선언을 해주었는데, 나는 useRef를 이용해 스크롤 값이 변경될 때마다 current로 스크롤 값을 불러와 다뤄주었다.
const [isNavOn, setIsNavOn] = useState(true);
//이전 스크롤 초기값
const beforeScrollY = useRef(0);
useEffect(() => {
window.addEventListener("scroll", scrollEvent);
if (token) {
setIsLoggedIn(true);
} else {
setIsLoggedIn(false);
}
}, [isLoggedIn, setIsLoggedIn, token]);
const scrollEvent = useMemo(
() =>
throttle(() => {
const currentScrollY = window.scrollY;
if (beforeScrollY.current < currentScrollY) {
setIsNavOn(false);
console.log("내림");
} else {
setIsNavOn(true);
console.log("올림");
}
//이전 스크롤값 저장
beforeScrollY.current = currentScrollY;
}, 300),
[beforeScrollY]
);
return(
<div className={isNavOn ? "main__first" : "main__first hidden"}>
의존성 배열 안에는 렌더링과 관련된 값을 넣어주어야 하기에 beforeScrollY를 넣어 주었다.
이렇게 코드를 수정하니 콘솔에 나타나던 오류도 없어졌고 실행도 정상적으로 되는 것을 확인 할 수 있었다.
크 감사합니다!