Preact 디바운싱 가이드
디바운싱은 설정된 시간 동안 호출이 멈출 때까지 함수 실행을 지연합니다. 새 호출이 발생할 때마다 타이머가 다시 시작됩니다. 기본 설정에서는 가장 최근 호출의 인수를 사용해 해당 호출만 실행합니다.
중간 호출을 버려도 되고 최종 값이 중요할 때 디바운싱을 사용합니다. 검색 입력, 폼 검증, 자동 저장, 크기 변경 처리가 일반적인 예입니다.
디바운싱의 작동 방식
아래 타임라인은 호출이 버스트 형태로 들어오는 모습을 보여 줍니다. 모든 호출은 타이머를 재설정합니다. 각 버스트의 마지막 호출은 세 틱 동안 활동이 없으면 실행됩니다.
Debouncing (wait: 3 ticks)
Timeline: [1 second per tick]
Calls: ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Executed: ❌ ❌ ❌ ❌ ❌ ❌ ❌ ❌ ⏳ -> ✅ ❌ ⏳ -> ✅
[================================================================]
^ Executes here after
3 ticks of no calls
[Burst of calls] [More calls] [Wait] [New burst]
No execution Resets timer Execute Reset and execute
각 버스트에서 가장 최근 호출만 실행됩니다. 이전의 모든 호출은 버려집니다.
디바운싱은 의도적으로 일부 호출을 버립니다. 모든 작업을 반드시 실행해야 한다면 큐잉을 사용합니다.
디바운싱을 사용해야 하는 경우
다음과 같은 경우 디바운싱을 선택합니다.
- 활동이 멈출 때까지 기다리려는 경우
- 가장 최근 인수만 중요한 경우
- 모든 이벤트마다 작업을 반복하면 리소스가 낭비되는 경우
- 짧은 지연을 허용할 수 있는 경우
다음과 같은 경우 다른 유틸리티를 선택합니다.
- 활동이 계속되는 동안 작업을 일정한 간격으로 실행해야 하는 경우 스로틀링을 사용합니다.
- 시간 창 안에서 정해진 수의 호출을 실행할 수 있어야 하는 경우 요청률 제한을 사용합니다.
- 모든 작업을 결국 실행해야 하는 경우 큐잉을 사용합니다.
- 여러 항목을 함께 처리해야 하는 경우 배칭을 사용합니다.
- 결과를 기다리거나 오류를 처리하고 재시도하거나 진행 중인 작업을 중단해야 하는 경우 비동기 디바운싱을 사용합니다.
API 선택하기
- 안정적인 디바운스 이벤트 핸들러가 필요하면
useDebouncedCallback - 지연된 Preact 상태가 필요하면
useDebouncedState또는useDebouncedValue - 수명 주기 메서드와 선택한 상태가 필요하면
useDebouncer
이벤트 핸들러에는 콜백 API를, 지연된 UI 상태에는 상태 또는 값 API를 사용합니다. cancel(), flush(), 선택한 상태, 동적 옵션이 필요하면 인스턴스 API를 사용합니다.
Preact 예제
import { useDebouncedCallback, useDebouncer } from '@tanstack/preact-pacer'
function SearchBox() {
const search = useDebouncedCallback(runSearch, { wait: 300 })
const debouncer = useDebouncer(saveDraft, { wait: 500 }, (state) => ({
isPending: state.isPending,
}))
return (
<>
<input onChange={(event) => search(event.currentTarget.value)} />
<button
onClick={() => debouncer.flush()}
disabled={!debouncer.state.isPending}
>
Save now
</button>
</>
)
}
이 가이드에서 이후에 다루는 핵심 코드 조각은 useDebouncer를 사용하며 컴포넌트나 다른 훅 내부에서 실행한다고 가정합니다.
실행 타이밍
leading과 trailing 옵션은 대기 시간의 어느 시점에 실행할 수 있는지를 제어합니다.
leading | trailing | 동작 |
|---|---|---|
false | true | 활동이 멈출 때까지 기다린 다음 가장 최근 호출을 실행합니다. 기본 동작입니다. |
true | false | 첫 호출을 즉시 실행합니다. 이후 호출은 실행하지 않고 대기 시간을 다시 시작합니다. |
true | true | 첫 호출을 즉시 실행합니다. 대기 시간 중에 다른 호출이 들어오면 가장 최근 호출을 후행 시점에 실행합니다. |
false | false | 어떤 호출도 실행하지 않습니다. |
const debouncer = useDebouncer(saveDraft, {
wait: 1000,
leading: true,
trailing: true,
})
debouncer.maybeExecute('first') // Executes immediately.
debouncer.maybeExecute('second')
debouncer.maybeExecute('latest') // Executes after 1 second of inactivity.
두 시점을 모두 활성화하면 단일 호출은 선행 시점에만 실행됩니다. 후행 실행은 대기 시간 중에 다른 호출이 들어올 때만 발생합니다.
최대 대기 시간 없음
useDebouncer는 maxWait 옵션을 제공하지 않습니다. 호출이 계속 들어오면 후행 실행이 무기한 미뤄질 수 있습니다. 호출이 계속 들어오는 동안에도 제한된 간격으로 작업을 계속해야 한다면 스로틀링을 사용합니다.
대기 작업 제어하기
인스턴스 API는 대기 작업의 실행, 취소, 재설정을 구분합니다.
플러시
flush()는 대기 중인 후행 호출을 가장 최근 인수로 즉시 실행합니다. 대기 중인 후행 호출이 없으면 아무 동작도 하지 않습니다.
const debouncer = useDebouncer(saveDraft, { wait: 1000 })
debouncer.maybeExecute('draft')
debouncer.flush() // Executes saveDraft('draft') now.
취소
cancel()은 함수를 실행하지 않고 대기 중인 타임아웃을 지웁니다. 또한 다음에 maybeExecute()를 호출할 때 선행 호출이 즉시 실행될 수 있게 합니다.
debouncer.maybeExecute('discarded draft')
debouncer.cancel()
재설정
reset()은 디바운서의 상태 카운터와 플래그를 기본값으로 복원합니다. 이미 예약된 타임아웃은 지우지 않습니다. 대기 작업을 버리고 상태를 재설정해야 한다면 먼저 cancel()을 호출합니다.
debouncer.cancel()
debouncer.reset()
런타임에 동작 설정하기
생성 후 옵션을 변경하려면 setOptions()를 사용합니다.
debouncer.setOptions({
wait: 1000,
leading: true,
trailing: false,
})
새 wait 값은 다음 호출에서 타임아웃을 예약할 때 적용됩니다. 이미 대기 중인 타임아웃을 다시 예약하지는 않습니다. maybeExecute()를 다시 호출하면 이전 타임아웃을 지우고 현재 옵션을 사용해 새 타임아웃을 예약합니다.
활성화 및 비활성화
실행을 막으려면 enabled를 false로 설정합니다. setOptions()로 디바운서를 비활성화하면 대기 중인 호출도 취소됩니다.
const debouncer = useDebouncer(saveDraft, {
wait: 500,
enabled: false,
})
debouncer.maybeExecute('ignored')
debouncer.setOptions({ enabled: true })
debouncer.maybeExecute('saved')
enabled와 wait 옵션에는 디바운서 인스턴스를 받는 함수를 사용할 수도 있습니다.
const debouncer = useDebouncer(saveDraft, {
enabled: (debouncer) => debouncer.store.state.executionCount < 10,
wait: (debouncer) => (debouncer.store.state.executionCount === 0 ? 300 : 500),
})
실행 관찰하기
래핑된 함수가 실행된 후 부수 효과를 수행하려면 onExecute를 사용합니다. 콜백에는 실행된 인수와 디바운서 인스턴스가 차례로 전달됩니다.
const debouncer = useDebouncer(saveDraft, {
wait: 500,
onExecute: (args, debouncer) => {
console.log('Saved arguments:', args)
console.log('Execution count:', debouncer.store.state.executionCount)
},
})
Preact 수명 주기
어댑터는 소유자가 제거될 때 대기 작업을 취소합니다. onUnmount를 제공하면 이 기본 정리 동작을 대체하므로 사용자 정의 콜백에서 필요한 모든 수명 주기 작업을 수행해야 합니다. 사용자 정의 정리 과정에서 작업을 플러시하면 컴포넌트가 제거되는 동안 사용자 콜백이 실행될 수 있다는 점에 유의해야 합니다.
반응형 상태
어댑터는 셀렉터 인수가 반환한 상태만 구독합니다. 셀렉터가 없으면 어댑터 상태는 비어 있습니다. 컴포넌트나 다른 훅의 최상위 수준에서 유틸리티를 만들고 뷰에서 사용하는 필드만 선택합니다.
const debouncer = useDebouncer(saveDraft, { wait: 500 }, (state) => ({
isPending: state.isPending,
executionCount: state.executionCount,
}))
console.log(debouncer.state.isPending, debouncer.state.executionCount)
옵션 함수와 수명 주기 콜백은 기반 공개 유틸리티 인스턴스를 받습니다. 위 예제처럼 해당 콜백 안에서 .store.state를 읽는 방식을 지원합니다. 렌더링 코드는 여기에 나온 선택된 어댑터 상태를 읽어야 합니다.
앱에서 유지한 선택 상태를 복원하려면 initialState로 부분 스냅샷을 전달합니다. 이 스냅샷은 기본값과 병합됩니다. 지속 가능한 필드만 복원합니다. 대기 중인 타이머는 복원되지 않습니다.
isPending: 후행 실행이 대기 중인지 여부입니다.executionCount: 래핑된 함수가 실행된 횟수입니다.lastArgs: 후행 실행이 활성화된 가장 최근 호출에서 기록한 인수입니다. 대기 작업으로 간주하기 전에isPending을 확인합니다.status:'disabled','idle','pending'중 하나입니다.
어댑터 시그니처는 Preact API 레퍼런스를, 전체 옵션 및 상태 타입은 공개 코어 레퍼런스를 참고합니다.