포트폴리오 제작 중에 브라우저의 화면 너비를 구해서 너비에 따라 화면구성을 달리해줘야 했다. 그래서 window의 resize 이벤트를 사용하기로 하였다. 하지만 이대로 사용하였을 때 브라우저 창 resizing마다 콜백 코드가 계속해서 실행되었다. resizing마다 콜백 코드를 계속해서 실행해줘야할 수도 있지만 저는 resizing마다 실행할 필요가 없기 때문에 매우 비효율적이라 생각하였다. 그렇게해서 찾아보게 된 개념이 디바운싱과 쓰로틀링이다. 디바운싱, 쓰로틀링 두개 다 웹에서 발생하는 이벤트를 제어하는 방법이라고 한다.
디바운싱(Debouncing) - 연속으로 호출되는 함수들 중에 마지막에 호출되는 함수만 실행되도록 하는 것
쓰로틀링(Throttling) - 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것
처음엔 아래코드처럼 innerWidth를 그대로 상태로 담아서 사용하고 콘솔을 찍어 확인해보았더니 resizing마다 계속해서 로그가 찍히는 것을 볼 수 있었다. 그래서 브라우저 창을 늘리거나 줄이기를 끝낸 뒤에 이벤트를 실행하도록하여 최적화된 화면을 구성할 수 있을 것이다.
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
const handleResize = () => {
setScreenWidth(window.innerWidth);
};
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
아래처럼 setTimeout 메서드를 사용하여 최적화를 간단하게 할 수 있다. setTimeout메서드로 0.4초가 지나기 전 resize이벤트가 발생되면 이전에 설정해둔 내용을 clear 시키고 다시 새로운 resizeTimerRef를 설정해준다. 따라서 resizing마다 로그가 찍히지 않는 것이다.
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
const resizeTimerRef = useRef<NodeJS.Timeout | null>(null);
const handleResize = () => {
setScreenWidth(window.innerWidth);
console.log("resize");
};
useEffect(() => {
const handleResizeWithTimer = () => {
if (resizeTimerRef.current !== null) {
clearTimeout(resizeTimerRef.current);
}
resizeTimerRef.current = setTimeout(handleResize, 400);
};
window.addEventListener("resize", handleResizeWithTimer, false);
return () => {
if (resizeTimerRef.current !== null) {
clearTimeout(resizeTimerRef.current);
}
window.removeEventListener("resize", handleResizeWithTimer);
};
}, []);
또한, useEffect 훅을 사용하기 때문에 useEffect내부에서 변수를 선언하고 사용할 땐 주의해야한다.
useEffect는 렌더링 된 후 실행되기 때문에, 그 안에서 변수를 선언하면 매 렌더링마다 새로운 변수가 생성되기 때문에 원하는 결과를 얻을 수 없다. 그렇기 때문에 resizeTimer를 useRef로 관리하였다. useRef.current를 통해 변수의 값을 유지하면서 렌더링 사이에서 변경할 수 있었다.
참고