https://rajeshnaroth.medium.com/using-throttle-and-debounce-in-a-react-function-component-5489fc3461b3
위 글을 참고하였습니다.
search 기능을 구현할 때 debounce를 사용하곤 하는데 원하는대로 동작하지 않을 때가 있었다.
const [keyword, setKeyword] = useState('');
const search = _.debounce((keyword: string) => {
// search 로직
}, 500);
return <>
<input onChange={(e)=>{
setKeyword(e.target.value);
search(e.target.value);
}}
</>
위와 같은 코드가 있다고 해보자. 내가 기대하는 바는 두루미를 검색할 때 ㄷ, 두, ㄹ, 루
이런식으로 검색되는 게 아니라 두
, 두루
이런 식으로 검색 되는 것이다. 그걸 기대하며 debounce를 사용했는데 왜인지 ㄷ, 두, ㄹ, 루 이런 식으로 하나하나 검색이 되었다.
그래서 이리저리 검색해봤더니 위 글을 찾을 수 있었다. 원인은 아래와 같았다.
Throttle and debounce works using window.setTimeout() behind the scenes. Every time the function component is evaluated, you are registering a fresh setTimeout callback.
throttle과 debounce는 window.setTimeOut()을 사용하여 동작하는데 컴포넌트가 *re-evaluation 될때마다 새로운 setTimeOut 콜백을 등록한다고 한다.
따라서 어떻게든 Debounced Callback에 대한 참조를 저장해야 한다. 방법은 두가지가 있는데 아래에 소개해보겠다.
*Re-evaluating Components 와 Re-Rendering DOM의 차이
Component는 props,state, 또는 context가 바뀔때마다 re-evaluate된다 vs DOM은 evaluation 사이에 변화가 있을 때 re-render된다.
useRef()로부터 반환된 value는 re-evalutated 되지 않기 때문에 아래와 같이 구현하면 해결할 수 있다.
const [keyword, setKeyword] = useState('');
const search = useRef(_.debounce((keyword: string) => {
// search 로직
}, 500)).current;
return <>
<input onChange={(e)=>{
setKeyword(e.target.value);
search(e.target.value);
}}
</>
useCallback도 마찬가지이다. 이게 current를 사용하지 않고 더 직관적이기 때문에 위보다 좀 더 깔끔한 방법같다.
const [keyword, setKeyword] = useState('');
const search = useCallback(_.debounce((keyword: string) => {
// search 로직
}, 500),[]);
return <>
<input onChange={(e)=>{
setKeyword(e.target.value);
search(e.target.value);
}}
</>