React

[React] 리액트로 디바운스(Debounce) 구현하기

킹우현 2023. 5. 17. 15:54

이전에 Javascript Deep Dive 41장에서 타이머에 관한 내용을 다룬 적이 있다.( https://woohyun-king.tistory.com/136 )

 

[JS Deep Dive] 41. 타이머

41.1) 호출 스케줄링 함수를 명시적으로 호출하면 함수가 즉시 실행된다. 만약에 함수를 명시적으로 호출하지 않고 일정 시간이 경과된 후에 호출되도록 함수 호출을 "예약"하기 위해서는 Timer 함

woohyun-king.tistory.com

디바운스와 스로틀은 모두 짧은 시간 간격으로 연속으로 발생하는 이벤트 핸들러의 과도한 호출을 방지하기 위한 프로그래밍 기법이다.

 

그 중에 디바운스는 짧은 시간 간격으로 이벤트가 연속으로 발생하면 이벤트 핸들러를 호출하지 않다가, 일정 시간이 경과한 후에 이벤트 핸들러가 1번만 호출되도록 하는 방식으로 과도한 이벤트 핸들러의 호출을 방지한다. 즉, 짧은 시간 간격으로 이벤트가 발생한다면 일정 delay가 지났을 경우에 마지막으로 1번만 이벤트 핸들러가 호출된다.

 

디바운스는 이렇듯 Input 요소에 입력된 값으로 Ajax 요청을 하는 입력 필드 자동완성 UI 구현에 유용하게 사용되는데, 이번에는 React로 이를 구현해보고자 한다.

 

const debounce = (callback, delay) => {
	let timerId;
        // 디바운스 함수는 timerId를 기억하는 클로저를 반환한다.
        return event => {
            // delay가 경과하기 전에 이벤트가 발생하면 이전 타이머를 취소하고 새로운 타이머를 재설정
            // 따라서 delay보다 짧은 간격으로 이벤트가 발생하면 callback은 호출되지 않는다.
            if (timerId) clearTimeout(timerId);
            timerId = setTimeout(callback,delay,event);
}

// 디바운스 함수가 반환하는 클로저가 이벤트 핸들러로 등록된다.
// 300ms 보다 짧은 간격으로 input 이벤트가 발생하면 디바운스 함수의 콜백 함수는
// 호출되지 않다가 300ms 동안 input 이벤트가 더 이상 발생하지 않으면 1번만 호출된다.
$input.oninput = debounce(e => {
	$msg.textContent = e.target.value;
}, 300);

먼저 Javascript로 구현한 디바운스 코드이다. setTimeout 함수를 사용해서 특정 delay가 지나지 않았을 때 이벤트 핸들러가 호출된다면 clearTimeout에 timerId를 전달해서 타이머를 제거한 뒤 setTimeout함수를 재호출한다.

 

const [filterText, setFilterText] = useState("");
const [tempText, setTempText] = useState("");

  useEffect(() => {
    const debounce = setTimeout(() => {
      return setFilterText(tempText);
    }, 300);
    return () => clearTimeout(debounce);
  }, [tempText]);

  return (
    <div>
      <input
        type="text"
        value={tempText}
        onChange={(e) => setTempText(e.currentTarget.value)}
      />
      {countryList
        .filter((item) => item.startsWith(filterText))
        .sort()
        .map((country, index) => (
          <div key={index}>{country}</div>
        ))}
    </div>
  );
}

위 코드는 React로 구현한 디바운스 코드로, 국가들이 저장된 배열(countryList)에서 검색한 키워드로 시작하는 국가들만 정렬하여 UI로 그려주는 코드이다.

 

만약에 디바운스를 사용하지 않고 필터링한다면 Input 값이 변경될 때마다 필터링이 이루어질 것이다. (만약에 Ajax 요청이 이루어진다면 이는 서버에 부담을 줄 뿐더러, 성능에 영향을 줄 것이다.)

 

먼저 useEffect Hook을 사용하였는데, 검색어(tempText)에 입력이 이루어지고 나서 일정 delay가 지나지 않고 재호출(입력 변경)되었을 경우, return 되는 콜백 함수가 실행되면서 clearTimeout()을 통해 타이머를 제거한 뒤, 다시 setTimeout()가 호출될 것이다.

useEffect안에서 return을 하면 정리의 개념으로 실행이 된다. useEffect의 두번째 argument로 들어가는 array 값이 변하면 useEffect는 다시 콜백을 실행하는데 그때 그전 콜백의 리턴 값을 실행한다. ⭐️

 

그렇다면 일정 delay(300ms)가 지났을 경우에만 setFilterText 함수를 통해 실제 필터링이 이루어지도록 하는 텍스트(filterText)를 set해주게 되고, 필터링이 이루어질 것이다.

 

정리하자면, React에서 디바운스를 구현하기 위해서는 useEffect Hook에서 setTimeout() 함수의 콜백 함수 내에 Ajax 요청이나 디바운스 되어야 하는 이벤트를 호출하고, return 값에 콜백 함수를 호출 스케줄링한 타이머를 제거하는 clearTimout() 함수를 작성하면 된다.