Debounce와 Throttle

2022-01-26

디바운스(Debounce)와 쓰로틀(Throttle)은 예전부터 들어왔지만, 실무에서는 직접 구현하기 보다 lodash를 사용했기 때문에 정확히 어떻게 동작 되는지 몰랐다.

해당 글에서는 간단하게 useDebounceuseThrottle hooks를 만들어서 설명해보려 한다.

언제 사용하는가?

debounce와 throttle 모두 특정 함수의 호출 횟수를 줄여서, 웹 성능이 저하 되는 것을 방지하기 위해 사용된다.

예를 들어, 브라우저 크기 조절이나 지도에서 마커가 이동 중에는 주소가 변경되지 않는 것, 입력 창에 어떤 제품을 입력하고 몇 초 지나서 하단에 추천 검색어가 뜨는 것, 여러 번 클릭했음에도 한 번만 실행되는 것 등, 모두 어느 정도의 텀을 두고 유저에게 보여진다. 이처럼 주로 api 호출이나 DOM 이벤트의 실행을 줄일 때 사용된다.


필자가 생각한 '간단한' 정의는 아래와 같으며, 아래 hooks 예시를 통해 자세히 알아보자.

  • debounce: 일정 시간 이후에 함수를 호출한다.

  • throttle: 일정 시간마다 함수를 호출한다.

(아래 예시에서 일정 시간은 모두 1초다)


useDebounce

위 예시처럼, input에 입력한 text는 바로 바뀌지만, debounceText는 입력한 후에 일정 시간 이후에 바뀌는 것을 볼 수 있다.

useDebounce.ts

import { useEffect, useState } from 'react'

const useDebounce = (value: string, time: number) => {
  const [debouncedValue, setDebouncedValue] = useState(value)
  useEffect(() => {
    const timerId = setTimeout(() => {
      // 일정 시간 이후에 변경한다
      // 변경 사항이 생긴다면, 그로부터 time을 다시 센다
      return setDebouncedValue(value)
    }, time)
    // 기능 수행을 완료하면 구독을 해제한다
    return () => clearTimeout(timerId)
  }, [value, time])

  // 변경된 값을 return
  return debouncedValue
}

export default useDebounce

App.tsx

const debouncedText = useDebounce(text, 1000)

useDebounce는 변경된 값을 return하기 때문에 변수에 할당해서 사용할 수 있다. 여기서 주의할 점은, text에 변경사항이 생긴다면 그로부터 일정 시간이 지나야 함수를 호출한다는 점이다.

useThrottle

그에 반해, throttle은 일정 시간마다 함수를 호출한다.

useThrottle.ts

import { useEffect, useState } from 'react'

const useThrottle = (callbackFunc: () => void, time: number): any => {
  const [isWaiting, setIsWaiting] = useState(false)

  useEffect(() => {
    if (!isWaiting) {
      callbackFunc()
      setIsWaiting(true) // 함수가 호출되자마자 true로 바꾸어 호출 중단

      setTimeout(() => {
        // 특정 시간 이후에 false로 바꾸어 재호출
        setIsWaiting(false)
      }, time)
    }
  }, [callbackFunc, isWaiting, time])
}

export default useThrottle

형태도 useEffect와 비슷하다.

App.tsx

useEffect(() => {
  const interval = setInterval(() => setCount(count => count + 1), 100)
  return () => clearInterval(interval)
}, [])

useThrottle(() => {
  setThrottledCount(throttledCount + 1)
}, 1000)

debounce와 달리 연속적으로 api를 호출(ex. 추천검색어, 무한스크롤)이 필요할 때 일정 시간마다 호출하여 실행한다.

useThrottle은 최소한의 스펙을 구현하기 위해 작성한 것으로 추후 무한스크롤 관련 글 작성할 때 더 다뤄볼 예정이다.

문제점

debounce와 throttle 모두 setTimeout이라는 Web API에 의해 실행된다. 그러나 setTimeout, setInterval은 정확한 지연시간을 보장해주지 않는다. 실제로 useThrottle 예제를 몇 번 새로고침하다보면 count와 throttleCount의 숫자가 불규칙하게 변하는 것을 확인할 수 있다. 때문에 정교한 작업의 개발이 필요하다면 다른 방법을 찾아봐야 한다.


참고