목차
https://ggubbanglovesherlife.tistory.com/132
react로 검색기능 만들면서 삽질한 이야기 (feat. 디바운스)
※ 이 글은 저의 삽질 과정이 그대로 담겨있는 글로, 사담이 많습니다. 결과만 알고 싶으신 분은 최종 코드를 읽어주세요 검색창을 구현할 일이 생겼는데 글자가 하나 입력될때마다 결과를 다시
ggubbanglovesherlife.tistory.com
디바운싱과 쓰로틀링
로직이 실행되는 횟수를 제한하여 프론트엔드 성능 최적화를 위해 쓰인다
디바운싱과 쓰로틀링에 대해 말하기 전에 두가지 함수에 대해 알아야한다
setTimeout()
: 함수의 실행을 예약하는 타이머 기능. 브라우저에서 제공하는 Web API. 지정된 시간 이후에 함수를 실행한다.
const timeId= setTimeout(callbackFunction, timeout);
clearTimeout()
- 타이머의 실행을 취소하는 기능
- setTimeout()이 반환하는 양의 정수값을 인자로 받음
디바운스란?

스위치를 눌렀다 떼는 과정에서 전압이 불규칙적으로 들어가 전류의 흐름이 비정상적으로 일어나는 현상을 말한다.
이런 바운싱 현상을 방지하기 위한 것이 디바운싱 기법이다.
이게 프로그래밍이랑 무슨 상관인가? 라고 생각할 수도 있는데, 프로그래밍에서의 디바운싱도 비슷한 개념을 갖고 있다.
프로그래밍에서의 디바운싱
연이어 발생한 이벤트를 하나의 그룹으로 묶어서 처리하는 방식으로, 주로 그룹에서 처음이나 마지막으로 실행된 함수를 처리하는 방식으로 쓰인다
식당에서 손님이 주문을 하는 상황을 상상해보자. 손님이 국밥, 소주, 전골을 각각 시켰을 때 알바가 이 주문을 받는 방법은 두가지가 있다. 첫번째로, 주문이 하나 들어올 때마다 주방에 보고하는 방법이다. 두번째로, 주문이 끝날 때까지 기다렸다가 주방에 보고하는 방법이 있다. 당연히 두번째 방법이 더 효율적이다.
디바운싱은 이와 같은 원리이다. 입력이 끝날 때까지 기다렸다가 마지막 요청을 보낸다.
예를 들어, 타이머를 3초로 설정했다고 가정해보자.
0초에 이벤트가 발생하면 0+3초인 3초까지 다른 이벤트를 기다린다. 이벤트가 발생했으므로 새 타이머가 설정된다.
1초에 또 다른 이벤트가 발생하면 1+3초인 4초까지 다른 이벤트를 기다린다. 이벤트가 발생했으므로 이전 타이머를 초기화하고 새 타이머가 설정된다.
2초에 또 다른 이벤트가 발생하면 2+3초인 5초까지 다른 이벤트를 기다린다. 이전 타이머를 초기화하고 새 타이머가 설정된다.
5초까지 기다린 후 새 이벤트가 없다면 로직을 실행한다.

디바운싱의 종류
디바운싱은 이벤트를 처리하는 시점에 따라 Leading edge와 Trailing edge로 나눌 수 있다.
- Leading edge: 처음 실행하는 함수를 처리하고 그 뒤의 입력을 무시한다
- Trailing edge: 마지막으로 실행한 함수를 처리하고 그 전의 입력들을 무시한다

디바운싱 사용 예시
1. 창 크기에 따른 이벤트를 발생시킬 때 디바운싱을 사용하지 않으면 창 크기를 조정할 때마다 이벤트가 실행된다. 조정이 멈출 때까지 기다렸다가 마지막으로 변경된 창 크기를 기반으로 이벤트를 발생시키는 게 이상적이다.
2. 키보드 입력을 통해 이벤트를 발생시킬 때 디바운싱을 사용하지 않으면 음운이 바뀔 때마다 이벤트가 발생한다. useState를 배울 때 input창으로 입력한 데이터를 상태로 받고 콘솔창에 찍어본 경험이 있을 것이다. 만약 '강'을 입력한다고 하면 ㄱ ㅏ ㅇ 이렇게 추가될 때마다 콘솔창에 찍힌다. 한 글자를 치는데 세번의 리렌더링이 일어나는 것이다.
이번 프로젝트에서 검색 버튼을 누르지 않아도 입력값이 바뀔때마다 일치하는 결과값을 보여주는 검색창을 만들었다. 입력값이 변할 때마다 결과값이 달라지고, 거기에 따라 리렌더링이 되기 때문에 화면이 지나치게 깜빡거리는걸 볼 수 있다.

디바운싱을 적용하면 아래와 같이 입력이 끝날 때까지 기다렸다가 결과값을 반환하므로 조금 더 나은 사용자 경험을 제공할 수 있다.

디바운싱 코드
기본 코드는 다음과 같다.
useEffect(() => {
// delay 후 callback 함수를 실행하는 타이머 생성
const timer = setTimeout(() => callback(), delay);
// 클린업 함수 -> 해당 컴포넌트가 Unmount 될 때 실행
return () => clearTimeout(timer);
// 의존성 배열(dependency array)
// callback 혹은 delay에 변화가 생기면 해당 useEffect를 실행한다
}, [callback, delay]);
내 프로젝트 코드는 다음과 같다.
리액트에서 디바운스 기능을 구현한 사용자 정의 훅을 만들어서 썼다.
첫번째 인자값인 value는 디바운스를 적용할 값이고, 두번째 인자인 delay는 디바운스 지연 시간이다. 단위는 ms이다.
이 시간동안 추가 입력이 없다면 useState를 이용하여 상태를 업데이트 시켜준다.
만약 이 시간동안 추가적인 입력이 있다면 clearTimeout을 이용하여 현재 돌아가고 있는 타이머를 취소 시킨다.
- useEffect 훅은 value 또는 delay가 변경될 때마다 실행된다. 내부에서는 setTimeout을 사용해 지연 시간 후에 setDebounceValue를 호출하여 debounceValue를 업데이트한다. 이렇게 함으로써, 사용자가 입력을 멈춘 후 지정된 지연 시간이 지나면 최종 입력값으로 상태가 업데이트됩니다.
- useEffect의 반환 함수에서는 clearTimeout을 호출하여 컴포넌트가 언마운트되거나 value 또는 delay가 변경되어 새로운 타이머가 설정될 때 setTimeout의 시간을 초기화 시킨다. 이는 메모리 누수를 방지하고, 불필요한 상태 업데이트를 막기 위함이다.
- useEffect 내부에서 return을 하게 되면 컴포넌트가 제거될 때 해당 코드들이 실행됩니다.
쓰로틀링
출력을 조절한다는 뜻으로, 비행기 기체에서 레버를 이용하여 기체로 들어가는 연료를 조절하는 행동에서 유래했다.
프로그래밍에서의 쓰로틀링은 이와 유사한 개념을 갖고 있다.
프로그래밍에서의 쓰로틀링은 이벤트를 일정주기마다 발생하도록 하는 기술이다. 짧은 시간 내 과도한 이벤트 실행으로 인해 발생하는 성능 저하를 방지할 수 있다.
Trottle의 설정 시간으로 100ms를 준다면, 해당 이벤트는 100ms동안 최대한 한번만 발생하게 된다. 즉, 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않는다.

쓰로틀링 사용예시
- 등록해놓은 스크롤 이벤트리스너로 인해 성능 저하가 예상되는 경우 ex) 무한 스크롤
- 이벤트를 일정 주기마다 발생하게 만들 때
- 사용자가 특정 버튼을 더블 클릭할 때 요청이 한번만 들어가도록
쓰로틀링 코드
- 특정 이벤트가 발생했을 때, 해당 이벤트를 처리해 줄 함수를 동작시킬 타이머를 생성한다
- 생성된 타이머가 종료되지 않았다면, 새로운 타이머는 생성될 수 없다.
- 500ms가 지난 후에 타이머를 초기화하는 동시에 원하는 동작 실행. 시간이 지나 타이머 내부의 콜백 함수가 동작할 때 timer 변수를 null로 초기화한다.
const DELAY = 500;
const handleScroll = () => {
timer = null;
console.log('스크롤!');
}
let timer;
// 스크롤 이벤트가 발생했을 때
window.addEventListener('scroll', () => {
// 타이머가 없을 때, 타이머를 생성한다.
// 이는 이전 타이머의 수행이 종료된 후에나 해당 작업이 이뤄질 수
// 있다는 것을 의미한다.
if (!timer) timer = setTimeout(handleScroll, DELAY);
});
코드 실행 과정은 다음과 같다.
- setTimeout(callback, 500)
- setTimeout이 실행됐기 때문에 timer의 값이 할당되어있음 - 스크롤 이벤트를 발생시키면 타이머에 값이 있기 때문에 실행이 안됨
- 500ms 경과
- 타이머가 null로 초기화되고 console.log가 실행됨 - 다시 스크롤 이벤트 발생
- 타이머가 null인 상태이기 때문에 다시 실행됨
디바운싱 VS 쓰로틀링
디바운싱의 리딩엣지와 쓰로틀링은 작동 방식이 유사하기 때문에 헷갈릴 수도 있다. 첫 요청이 들어온 후 일정시간동안 모든 요청을 무시한다는 점에서 유사하다.
하지만, 쓰로틀링은 함수가 적어도 Xms마다 정기적으로 실행되는걸 보장한다.
카카오맵 지도를 드래그해서 범위 내에 있는 카페찾기를 예시로 들어보겠다.
드래그 이벤트가 발생할때마다 API가 호출된다면? 과부하가 발생할 수 있다. 따라서 최적화해줘야한다.
쓰로틀링을 사용한다면? 사용자가 드래그를 시작한지 3초마다 API 호출된다. 드래그를 하는 동안 사용자는 계속해서 현재 위치에 있는 카페들을 볼 수 있다.
디바운스를 적용하면? 사용자가 드래그를 멈출 때까지 API가 호출되지 않고 사용자가 드래그를 멈춘 후 최종 화면에 있는 범위 내에 있는 카페를 검색한다. 쓰로틀링에 비해 API 호출 횟수가 적다는 장점이 있지만 드래그를 하는 도중에는 카페가 보이지 않는다.
따라서 사용자 경험을 우선시하면 쓰로틀링을 적용, 서버 비용을 줄이고 싶으면 디바운스를 적용하는 게 좋다.
만약 타이머를 0초로 설정한다면?
디바운싱에서 딜레이를 생략하거나 0으로 설정하면 이론적으론 즉시, 정확히는 다음 이벤트 사이클에 실행된다. setTimeout(0)이 실행되면 web API가 바로 종료되고 큐에 setTimeout 콜백을 넣는다.
이벤트 루프는 스택이 비워질때까지 기다린 후에 큐에 있는 콜백을 스택에 쌓을 수 있다. 그러니까 callback 함수가 있는 스택이 비워질때까지 시간이 걸리기 때문에 delay가 0이라고 해도 실제로 0초가 아닐수도 있다.
참고 자료
- 어쨌든 이벤트 루프는 무엇입니까?
https://www.youtube.com/watch?v=8aGhZQkoFbQ&t=897s
- [10분 테코톡] 자스민의 디바운싱과 쓰로틀링
https://www.youtube.com/watch?v=By49qqkzmzA&t=3s
'나도 공부한다 > JS' 카테고리의 다른 글
클린코드 자바스크립트 강의 내용 정리 (0) | 2024.12.10 |
---|---|
var을 사용하면 안 되는 이유 (0) | 2022.11.14 |