Debounce
디바운스는 과도한 요청으로 발생할 수 있는 과부하, 성능의 저하 등을 개선할 수 있는 여러 방법 중 하나이다.
가장 간단한 예로 input
에 키워드를 연속적으로 입력할 때 이벤트 변경이 발생할 때마다 네트워크 요청이 발생하는데,
이러한 경우에는 지속적인 요청으로 네트워크 용량을 잡아먹을 뿐만 아니라 백엔드 등 여러 곳에서 과부하가 발생하므로 성능에 좋지 않다.
이를 개선하기 위해 Debounce를 사용하여, 연속적으로 발생하는 이벤트 중 마지막 이벤트를 기점으로 네트워크 요청을 하도록 개선할 수 있다.
적용 전 검색 과정
'd' 입력 > 네트워크 요청 > 'e' 입력 > 네트워크 요청 > 'b' 입력 > 네트워크 요청 > ...
적용 후 검색 과정
'debounce'를 연속적으로 입력 > n초 내 새로운 이벤트가 발생하지 않으면 네트워크 요청
번외로 디바운스와 유사한, 유용하게 사용할 수 있는 성능 개선 방법 중 throttle이 있다. debounce는 이벤트 발생이 멈출 때 처리한다면, throttle 은 이벤트가 지속적으로 발생하더라도 일정한 간격으로 끊어서 처리한다.
Debounce & Throttle
debounce와 throttle 의 차이를 자세히 설명한 글
https://redd.one/blog/debounce-vs-throttle
debouce와 throttle 를 간단하게 요약하자면 다음과 같다.
- debounce: 일련의 연속된 이벤트에서 마지막 이벤트만 처리하도록 제어하는 기능으로, 사용자 입력과 같은 빈번한 이벤트를 처리할 때 사용한다.
- throttling: 정해진 일정 간격으로 요청을 처리한다. 따라서 정기적으로 실행해야한다면 throttle로 처리하는 것이 적합하다.
개선할 검색기능 코드
다음 코드는 swr을 사용한 코드이다. 이 예제는 input
태그에 onChange
이벤트가 발생할 때마다 swr이 새로운 api 요청을 보내므로 성능에 좋지 않은 코드다.
import { useState } from 'react';
import useSWR from 'swr';
export default function Search() {
const [keyword, setKeyword] = useState<string>('');
const { data } = useSWR(`/api/search/${keyword}`);
return (
<form onSubmit={(e) => e.preventDefault()}>
<input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
</form>
);
};
디바운스로 검색기능 개선
vercel swr의 해당 이슈를 참고했다.
https://github.com/vercel/swr/issues/110
1. useDebounce hook
import { useState, useEffect } from 'react';
export default function useDebounce(value: string, delay: number = 500) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debounced;
}
useEffect를 사용하여 커스텀 훅을 만들어준다. useDebounce 훅의 인자로 value와, delay 넘겨 값과 시간을 지정해준다.
value와 delay가 변경될 경우 setTimeout
실행하여 지정한 delay 시간이 되면 debounced 상태값을 변경해준다.
만약, setTimeout이 끝나지 않은 이전 handler가 있다면 clearTimeout으로 제거하여 마지막 요청만 실행될 수 있도록 한다.
2. useDebounce 사용
import { useState } from 'react';
import useSWR from 'swr';
import useDebounce from '@/hooks/debounce';
export default function Search() {
const [keyword, setKeyword] = useState<string>('');
const debouncedValue = useDebounce(value);
const { data } = useSWR(`/api/search/${debouncedValue}`);
return (
<form onSubmit={(e) => e.preventDefault()}>
<input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)} />
</form>
);
};
리액트 훅처럼 사용 방법 또한 매우 간단하다.
useDebounce
커스텀 훅으로 input value를 인자로 넘겨 마지막 요청에 값을 업데이트하여 debouncedValue
에 저장했다.
이제 swr이 debouncedValue
값이 업데이트 될 때만 실행되도록 개선되었다.