본문으로 건너뛰기

React 스로틀링 가이드

스로틀링은 호출이 계속 들어오는 동안 함수의 실행 빈도를 제한합니다. 디바운싱과 달리 활동이 멈출 때까지 기다리지 않습니다. 제한된 실행 간격을 만들어 연속적인 이벤트와 업데이트를 효과적으로 처리합니다.

기본 설정에서는 첫 호출이 즉시 실행됩니다. 대기 시간 중에 들어온 호출은 가장 최근 인수를 사용하는 하나의 후행 실행으로 통합됩니다.

스로틀링의 작동 방식

아래 타임라인은 세 틱마다 한 번의 실행을 허용하는 스로틀러를 보여 줍니다.

Throttling (one execution per 3 ticks)
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Executed: ✅ ❌ ⏳ -> ✅ ❌ ❌ ❌ ✅ ✅
[================================================================]
^ At most one execution per interval

[First burst] [More calls] [Spaced calls]
Execute first Keep latest trailing Execute when allowed

호출이 버려질 수 있지만 활동이 계속되는 동안 예측 가능한 간격으로 실행이 이어집니다.

스로틀링을 사용해야 하는 경우

다음과 같은 경우 스로틀링을 선택합니다.

  • 이벤트가 들어오는 동안 작업을 계속해야 하는 경우
  • 실행 사이에 최소 간격을 두어야 하는 경우
  • 중간 호출을 버려도 되는 경우
  • 첫 호출의 즉각적인 피드백이 유용한 경우

다음과 같은 경우 다른 유틸리티를 선택합니다.

  • 활동이 멈출 때까지 작업을 기다려야 하는 경우 디바운싱을 사용합니다.
  • 시간 창 안에서 정해진 수의 호출을 실행할 수 있어야 하는 경우 요청률 제한을 사용합니다.
  • 모든 작업을 결국 실행해야 하는 경우 큐잉을 사용합니다.
  • 여러 항목을 함께 실행해야 하는 경우 배칭을 사용합니다.
  • Promise 결과, 재시도, 중단 지원이 필요한 경우 비동기 스로틀링을 사용합니다.

API 선택하기

  • 안정적인 스로틀 이벤트 핸들러가 필요하면 useThrottledCallback
  • 스로틀된 React 상태가 필요하면 useThrottledState 또는 useThrottledValue
  • 수명 주기 메서드와 선택한 상태가 필요하면 useThrottler

이벤트 핸들러에는 콜백 API를, 요청률이 제어되는 UI 상태에는 상태 또는 값 API를 사용합니다. 수명 주기 메서드와 타이밍 상태가 필요하면 인스턴스 API를 사용합니다.

React 예제

import { useThrottledCallback, useThrottledValue } from '@tanstack/react-pacer'

function ScrollStatus({ position }: { position: number }) {
const report = useThrottledCallback(sendPosition, { wait: 250 })
const [displayedPosition] = useThrottledValue(position, { wait: 100 })

return (
<button onClick={() => report(position)}>Report {displayedPosition}</button>
)
}

이 가이드에서 이후에 다루는 핵심 코드 조각은 useThrottler를 사용하며 컴포넌트나 다른 훅 내부에서 실행한다고 가정합니다.

실행 타이밍

leadingtrailing 옵션은 스로틀 간격의 어느 시점에 실행할 수 있는지를 제어합니다.

leadingtrailing동작
truetrue첫 호출을 즉시 실행하고 차단된 가장 최근 호출을 후행 시점에 실행합니다. 기본 동작입니다.
truefalse허용되는 즉시 실행하고 해당 간격 중의 호출은 버립니다.
falsetrue첫 실행을 후행 시점까지 지연하고 해당 간격 중에 받은 가장 최근 인수를 사용합니다.
falsefalse어떤 호출도 실행하지 않습니다.
const throttler = useThrottler(updateProgress, {
wait: 1000,
leading: true,
trailing: true,
})

throttler.maybeExecute(10) // Executes immediately.
throttler.maybeExecute(20)
throttler.maybeExecute(30) // Executes at the trailing edge with 30.

기존 간격 중에 들어온 호출은 해당 간격을 다시 시작하지 않고 후행 인수를 업데이트합니다. 이 점이 디바운싱과의 핵심 차이입니다.

대기 작업 제어하기

플러시

flush()는 대기 중인 후행 호출을 즉시 실행합니다. 대기 중인 후행 호출이 없으면 아무 동작도 하지 않습니다.

throttler.maybeExecute(10) // Leading execution.
throttler.maybeExecute(20) // Pending trailing execution.
throttler.flush() // Executes with 20 now.

취소

cancel()은 대기 중인 후행 호출을 버리고 저장된 인수를 지웁니다. 가장 최근에 완료된 실행의 타이밍은 재설정하지 않습니다.

throttler.maybeExecute(20)
throttler.cancel()

재설정

reset()은 상태 카운터와 타이밍 값을 기본값으로 복원합니다. 이미 예약된 타임아웃은 지우지 않습니다. 대기 작업을 버려야 한다면 cancel()을 호출한 뒤 reset()합니다.

throttler.cancel()
throttler.reset()

런타임에 동작 설정하기

생성 후 옵션을 업데이트하려면 setOptions()를 사용합니다.

throttler.setOptions({
wait: 250,
trailing: false,
})

변경된 wait 값은 기존 후행 타임아웃을 다시 예약하지 않습니다. 이후 예약과 실행에 적용됩니다.

enabledwait 옵션에는 스로틀러 인스턴스를 받는 함수를 사용할 수 있습니다.

const throttler = useThrottler(updateProgress, {
enabled: (throttler) => throttler.store.state.executionCount < 100,
wait: (throttler) => (throttler.store.state.executionCount < 10 ? 100 : 250),
})

setOptions()로 스로틀러를 비활성화하면 대기 중인 후행 실행이 취소됩니다.

실행 관찰하기

onExecute는 래핑된 함수가 실행된 후 호출되며 실행된 인수와 스로틀러 인스턴스를 차례로 받습니다.

const throttler = useThrottler(updateProgress, {
wait: 100,
onExecute: (args, throttler) => {
console.log('Rendered value:', args[0])
console.log('Executions:', throttler.store.state.executionCount)
},
})

React 수명 주기

어댑터는 소유자가 제거될 때 대기 작업을 취소합니다. onUnmount를 제공하면 이 기본 정리 동작을 대체하므로 사용자 정의 콜백에서 필요한 모든 수명 주기 작업을 수행해야 합니다. 사용자 정의 정리 과정에서 작업을 플러시하면 컴포넌트가 제거되는 동안 사용자 콜백이 실행될 수 있다는 점에 유의해야 합니다.

반응형 상태

어댑터는 셀렉터 인수가 반환한 상태만 구독합니다. 셀렉터가 없으면 어댑터 상태는 비어 있습니다. 컴포넌트나 다른 훅의 최상위 수준에서 유틸리티를 만들고 뷰에서 사용하는 필드만 선택합니다.

const throttler = useThrottler(updateProgress, { wait: 100 }, (state) => ({
isPending: state.isPending,
executionCount: state.executionCount,
}))

console.log(throttler.state.isPending, throttler.state.executionCount)

옵션 함수와 수명 주기 콜백은 기반 공개 유틸리티 인스턴스를 받습니다. 위 예제처럼 해당 콜백 안에서 .store.state를 읽는 방식을 지원합니다. 렌더링 코드는 여기에 나온 선택된 어댑터 상태를 읽어야 합니다.

앱에서 유지한 선택 상태를 복원하려면 initialState로 부분 스냅샷을 전달합니다. 이 스냅샷은 기본값과 병합됩니다. 지속 가능한 필드만 복원합니다. 대기 중인 타이머는 복원되지 않습니다.

  • isPending: 후행 실행이 대기 중인지 여부입니다.
  • lastArgs: 가능한 후행 실행을 위해 보존한 인수입니다.
  • lastExecutionTime: 래핑된 함수가 마지막으로 실행된 시점입니다.
  • nextExecutionTime: 다음 실행이 가능한 시점입니다.
  • executionCount: 래핑된 함수가 실행된 횟수입니다.
  • status: 'disabled', 'idle', 'pending' 중 하나입니다.

어댑터 시그니처는 React API 레퍼런스를, 전체 옵션 및 상태 타입은 공개 코어 레퍼런스를 참고합니다.