실시간 검색 기능과 버튼에서 연속된 동작으로 인한 불필요한 네트워크 요청을 방지해야하는 상황
특정 시간 이후에 한 번만 실행하기
Debounce 는 여러 번 발생하는 이벤트에서, 가장 마지막 이벤트만을 실행되도록 만드는 개념이다.
아래 예시 그럼처럼 순차적 호출을 하나로 그룹화할 수 있다.
일정한 간격으로 한 번만 실행하기
Throttle 는 여러 번 발생하는 이벤트를 일정 시간동안, 한 번만 실행되도록 만드는 개념이다.
예를 들어, Throttle 의 설정시간으로 3ms 를 주게되면 해당 이벤트는 3ms 동안 최대 한번만 발생하게 된다.
이벤트를 언제 발생 시킬지의 시점 차이
Debounce 는 마지막 요청이 끝날 때까지 무한으로 기다리지만, Throttle 는 요청이 시작되면 일정 주기로 계속 실행한다.
Debounce 의 시간을 짧게 가져간다면 Throttle 와 비슷한 효과가 날 수 있지만, 그럼에도 시점에서 차이가 날 수 있기 때문에 결과물의 성격에 따라 사용 방법이 달라질 수 있다.
대표적인 예시로, 자동완성을 만들 때 각 기능별 차이는 아래와 같다.
이때 유료 API라면 요청 하나하나가 모두 돈이기 때문에 디바운싱이 더 적절한 선택이다.
사용자가 검색어를 입력할 때마다 API를 호출하면 성능에 영향을 미치므로, 입력이 끝난 후 일정 시간 동안 대기하여 최종 입력값으로만 API 호출을 수행하도록 결정.
기대 효과: 불필요한 API 호출 제거, 성능 최적화
import { useEffect, useState } from "react";
const useDebounce = <T>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
export default useDebounce;
const [searchTerm, setSearchTerm] = useState(initialSearchQuery);
const debouncedSearchTerm = useDebounce(searchTerm, 200);
버튼을 빠르게 연속 클릭할 경우 중복 동작이 발생하거나 서버 부하를 유발할 수 있으므로, 지정된 시간 간격으로 한 번만 이벤트가 실행되도록 제한
기대 효과: 버튼 동작 안정성 보장, 중복 요청 방지
import { useCallback, useEffect, useRef } from "react";
export function useThrottle() {
// 마지막으로 함수가 실행된 시간을 지정하는 ref -> ref를 사용하여 리랜더링 간에 값을 유지
const lastRun = useRef<number>(Date.now());
// 현재 대기 중인 타이머를 추적하는 ref
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
// 컴포넌트 언마운트 시 타이머 정리 -> 메모리 누수 방지
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return useCallback((callback: () => Promise<void>, delay: number) => {
const execute = () => {
callback();
lastRun.current = Date.now();
};
// 마지막 실행 이후 경과된 시간 계산
const timeElapsed = Date.now() - lastRun.current;
if (timeElapsed >= delay) {
execute();
} else {
// 이전에 예약된 타이머가 있다면 취소
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// 새로운 타이머 설정
timeoutRef.current = setTimeout(() => {
execute();
}, delay - timeElapsed);
}
}, []);
}
const handleButtonClick = useCallback(
(action: () => Promise<void>) => {
throttle(async () => {
if (isBtnDisabled) return;
setIsBtnDisabled(true);
try {
await action();
} finally {
setIsBtnDisabled(false);
}
}, 2000);
},
[isBtnDisabled, throttle]
);