디바운스(Debounce)와 쓰로틀링(Throttling)은 자바스크립트에서 이벤트 핸들링과 함수 실행을 제어하는 데 사용되는 두 가지 기술입니다.
프론트엔드 개발자라면 다들 개념을 알고 있을 것이지만 비전공자 입장에서는 이조차 낮선 개념입니다. 오늘은 쓰로틀링과 디바운스에 대해서 알아보도록 합시다.
디바운스이라는 용어는 전자 회로에서 스위치를 눌렀다 떼는 과정에서 스위치가 통통 튀며 전압이 불규칙적으로 들어가 전류의 흐름이 비정상적으로 일어나는 현상을 바운싱 현상이라하는데 이를 정상적으로 해주는 의미에서 사용되기 시작했다고합니다.
쓰로틀링이라는 용어는 비행기 또는 자동차 등에서 연료량을 조절하는 레버가 있는데 이를 당기거나 밀어서 연료량을 조절하는 것입니다. 이러한 동작과 유사한 방식으로 동작시키기 때문에 쓰로틀링이라는 용어가 사용되기 시작했다고합니다.
디바운스(debounce)와 쓰로틀링(throttle)의 차이점
두 기술은 비슷한듯 하지만 확실한 차이점이 존재합니다.
- 디바운싱 : 연속적으로 발생하는 이벤트에서 마지막 이벤트가 발생한 후 일정 시간이 지난 후에 수행
- 쓰로틀링 : 연속해서 발생하는 이벤트에서 일정 간격마다 이벤트에 대한 처리를 수행
타이머를 활용하여 구현하는 기술이지만 두 기술의 차이점은 디바운스는 마지막 이벤트 발생 후 일정 시간이 지난 후에 실행되지만, 쓰로틀링은 일정한 간격마다 실행됩니다.
예를 들어 쓰로틀링과 디바운스 모두 대기시간을 10초로 설정한 후 연속적으로 계속해서 함수 요청을 보낸다면
- 디바운스: 10초가 지나기 전에 요청이 발생하면 대기시간을 초기화하여 다시 10초의 대기시간을 기다린 후 실행됨
- 쓰로틀링 : 10초마다 한번씩 함수가 실행됨
쓰로틀링은 첫 요청을 받은 후 대기시간 동안 요청을 받지 않습니다. 반면 디바운스는 요청을 계속 받아들이지만 마지막 요청
만을 실행합니다.
대기시간을 설정하는 방법
자바스크립트에서는 아래 두 함수를 활용합니다.
- setTimeout() - 함수의 실행을 예약하는 타이머 기능
- clearTimeout() - 타이머의 실행을 취소하는 기능
setTimeout() 함수는 아래와 같이 사용합니다.
setTimeout(callback, delay);
여기서 callback은 실행될 함수를 나타내며, delay는 밀리초 단위로 지연될 시간을 나타냅니다.
예제 :
function sayHello() {
console.log("Hello, world!");
}
setTimeout(sayHello, 2000); // 2000밀리초(2초) 후에 sayHello 함수 실행
timeout을 생략하거나 0으로 설정하면 즉시, 정확히는 다음 이벤트 사이클에 실행합니다. 하지만 딜레이를 0으로 설정해도 즉시 실행되지 않을 수 있습니다. 이는 자바스크립트의 이벤트 루프와 관련이 있는데요. 자바스크립트는 단일 쓰레드로 동작합니다. 따라서, 대기시간이 종료되기 전 다른 작업이 진행 중이라면 해당 작업이 완료된 후 동작할 수 있습니다.
자세한 내용은 어쨋든 이벤트 루프란 무엇입니까? 동영상을 참고하면 좋을 것 같습니다.
clearTimeout() 타이머가 완료되기 전에 실행된다면 타이머를 취소하고 콜백함수를 실행하지 않도록 합니다.
const timerId = setTimeout(sayHello, 2000);
clearTimeout(timerId); // 타이머 취소
디바운스 - 연이은 요청 중“마지막 요청”으로부터 일정 시간 이후에 한번만 실행하기
디바운스에 대한 설명을 한줄로 요약하자면 위와 같습니다.
디바운스의 타임아웃을 500ms로 설정했다고 가정했을때 사용자가 500ms 내에 5번 버튼을 클릭했다고 한다면 디바운스는 사용자가 5번째에 클릭한 이벤트만을 500ms 이후 실행합니다.
즉 여러번 클릭했다고 하더라도 함수는 한번만 실행됩니다.
let timerId = null;
function debouncing (delay) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(()=>{
/* 실행할 코드 */
timerId = null;
}, delay);
}
디바운스는 어디에서 사용하나요?
디바운스는 성능 뿐만 아니라 유료 API를 사용할 때 많이 사용합니다. 요청 하나 하나가 모두 비용으로 연결되기 때문입니다.
따라서 디바운스는 주로 연이어 발생하는 이벤트를 단일 이벤트로 만들고 싶은 곳에 사용하게 됩니다. 대표적인 예로 타이핑 이벤트의 결과 표시 같은 것과 블로그의 자동 저장 기능 같은 것이 있습니다.
쓰로틀링 - 연속된 요청을 ”일정한 간격”으로 한번만 실행하기
쓰로틀링은 사용자가 이벤트를 몇번이나 발생시키던지 일정한 간격을 두고 한번만 실행하도록 하는 기법입니다.
슈팅 게임에서 버튼을 아무리 빠르게 연속으로 누르더라도 총알이 정해진 일정한 간격으로 발사되는 것과 같은 것입니다.
let timerId = null;
function throttling(delay) {
if (timerId) {
return;
}
timerId = setTimeout(() => {
/* 실행할 코드 */
timerId = null;
}, delay);
}
쓰로틀링은 어디에서 사용하나요?
쓰로틀링은 연속으로 발생하는 이벤트의 간격을 조절하고 싶을 때 사용합니다.
스크롤 이벤트나 마우스 움직임 이벤트와 같이 연속적으로 이벤트가 발생하는 곳에서 사용합니다.
대표적으로 무한 스크롤 기능 혹은 지도 어플리케이션에서 타일 이동 이벤트가 있을 수 있습니다.
React에서의 주의사항
일반적인 자바스크립트에서는 위에서 작성한 코드를 그대로 사용해도 큰 문제는 없겠지만 리액트에서는 조금 이야기가 달라집니다.
리액트에서는 페이지 이동시 컴포넌트가 언마운트됩니다.
만약 페이지를 이동하기 전 clearTimeout을 해주지 않고 이동했다면 타이머는 여전히 동작하고 있게됩니다.
이는 메모리 누수가 있다라고 말할 수 있을 것입니다.
여기에 더해서 리액트는 상태가 변경되면 리랜더링이 발생합니다.
리랜더링이 발생한다는 것은 timerId가 다시 null이 되어 버리므로 올바르게 동작하지 않게 됩니다. 이를 방지하기 위해서 메모이제이션을 활용할 필요가 있습니다.
메모리 누수 방지하기
위에서 언급한 메모리 누수를 방지하기 위해서는 컴포넌트가 언마운트될 때 clearTimeout을 해주도록 설정해 주면 됩니다.
useEffect를 활용하면 간단하게 처리가 가능합니다.
useEffect(()=>{
return ()=> {
if(timerId) {
clearTimeout(timerId);
}
}
},[])
useEffect의 return문은 컴포넌트가 언마운트될 때 동작하므로 페이지를 이동하는 등 컴포넌트가 언마운트 될 때 타이머를 취소하는 동작을 수행할 수 있습니다.
리랜더링으로 인한 오동작 방지하기
상태값을 변경하더라도 의도한 대로 쓰로틀링과 디바운스가 동작하도록 하기 위해서 메모이제이션을 활용해야합니다.
useCallback을 활용해서 debounce를 기억시켜 준다면 클로저 함수가 외부 변수에 대한 참조를 계속 가지고 있기 때문에 timerId를 기억하고 있게 됩니다.
const debounce = (callback, delay) => {
let timerId = null;
return (...args) => {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => {
callback(...args);
}, delay);
};
};
const handleFunc = useCallback(
debounce((value) => setState(value), 2000),
[]
);
디바운스 함수를 직접 작성하지 않더라도 Lodash와 같은 라이브러리를 통해서 손쉽게 구현할 수도 있습니다.
결론
프론트엔드 개발자라면 반드시 알고 있어야할 쓰로틀링과 디바운스에 대해서 다시 알게된 시간이였습니다.
특히 리액트에서 사용할 때의 메모리 누수와 상태값 변경에 따른 리랜더링으로 오동작을 방지하는 방법은 좋은 공부가 되었습니다.
'Develop > TIL' 카테고리의 다른 글
[TIL]Redux-persist 새로고침이 발생해도 store state 유지하기 (0) | 2023.12.03 |
---|---|
[TIL] CSS in JS의 성능에 대한 이야기 (0) | 2023.09.09 |
[TIL] Hamming Codes: 해밍 코드 (0) | 2023.08.27 |
-이사 완료- [TIL] Git - 좋은 커밋 메세지 작성 방법 (0) | 2023.08.21 |
항해99 실전 프로젝트를 앞두고 (0) | 2023.06.09 |