디바운싱과 쓰로틀링은 dom이벤트를 기반으로 실행하는 자바스크립트를 성능 상의 이유로 양적인 측면, 즉 자바스크립트의 이벤트를 제어(제한)하는 방법이다.
디바운싱: 연이어 호출되는 함수들 중 마지막 함수만 호출하는 것
쓰로틀링: 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출하지 않는 것
오늘 다뤄볼 디바운싱은 보통 input의 onChange이벤트를 기반으로 검색기능을 구현할 때 사용한다. 예를 들어 유저이름을 검색하는 기능을 구현한다고 했을 때, 알파벳 하나하나 자음 모음 하나하나 api 요청을 날리게 된다.
이런 식으로 기능을 구현하게 된다면 성능 저하는 물론이고 유료 api를 사용할 경우, 쿼리마다 비용이 책정되기 때문에 손해가 발생한다. 따라서 입력이 다 끝난 후에 요청을 보낼 수 있도록 타이머를 설정하고, 일정 시간동안 입력이 없으면 사용자가 입력을 끝마쳤다고 가정하여 api 요청을 보내는 것이다. 아래 예제 코드를 살펴보자.
먼저 예제 코드를 살펴보겠다.
https://codesandbox.io/s/usedebounce-mq14j?file=/src/App.js:0-775
import { useState, useEffect } from "react";
function useDebounce(value, delay = 1000) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value]);
return debouncedValue;
}
export default function App() {
const [inputValue, setInputValue] = useState("");
const fetchValue = useDebounce(inputValue);
const inputHandler = (e) => {
setInputValue(e.target.value);
};
return (
<div className="App">
<input type="text" onChange={inputHandler} />
<div>realtime Value: {inputValue}</div>
<div>current Value: {fetchValue}</div>
</div>
);
}
useEffect
useEffect hook의 첫 번째 인자로 state 객체를 업데이트하는 setState로 넣었다. 그리고 두 번째 인자의 의존성 배열로 value를 넣어 첫 렌더링, 그리고 value가 바뀔 때마다 실행되도록 했다.
cleanup 함수
useEffect에서는 함수를 리턴하여 뒷정리를 할 수 있다. 보통 setInterval이나 setTimeout을 통한 작업을 정리할 때 사용한다. 이 때, 의존성 배열이 비어있을 경우 컴포넌트가 언마운트될 때 실행된다.
현재 의존성 배열에 value를 넣어두어 첫 렌더링 이후 value가 변경될 때마다 실행된다. 따라서 1초 안에 다른 이벤트가 발생하지 않으면 기존 값이 return되고, 1초 안에 다른 이벤트가 발생할 경우 이전의 useEffect가 실행되고 clearTimeout이 작동하면서 setTimeout이 취소된다.
export default function App() {
const [keyword, setKeyword] = useState('');
const fetchKeyword = useDebounce(keyword);
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(event.target.value);
};
useEffect(() => {
fetch(`${API_ENDPOINT}/search?q=${keyword}`, {
method: 'GET',
headers: {
'Content-type': 'application/json',
},
})
.then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
});
}, [keyword]);
return (
<Container>
<input type="text" onChange={handleSearch} />
<div>realtime Value: {keyword}</div>
<div>current Value: {fetchKeyword}</div>
</Container>
);
}