Ditulis sendiri, bukan membungkus paket debounce mana pun. Itu keputusan yang disengaja: composable Kliping tidak boleh membawa dependensi runtime. Kalau memasang @kliping/use-debounce-fn memaksa orang menambah satu paket, mereka akan memakai paketnya langsung dan registry ini kehilangan alasan keberadaannya.
Yang didapat sebagai gantinya bukan cuma nol dependensi: timernya ikut mati bersama scope-nya. Debounce dari paket mana pun tidak tahu apa-apa soal siklus hidup Vue, jadi pemanggilnya harus ingat membatalkannya sendiri — dan itu jenis hal yang selalu terlupa.
wait boleh reaktif. Yang dibaca adalah nilainya saat run() dipanggil, bukan saat composable-nya dibuat.
Installation
pnpm dlx shadcn-vue@latest add @kliping/use-debounce-fn Perlu @kliping terdaftar di components.json project Anda. Lihat cara menyiapkannya.
Tanpa dependensi — tidak ada berkas lain yang ikut terpasang.
Salin kode berikut ke project Anda.
import { getCurrentScope, onScopeDispose, toValue } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
export interface UseDebounceFnOptions {
/** Invoke once on the leading edge, before the wait starts. Default: false. */
leading?: boolean
/** Invoke on the trailing edge of the wait. Default: true. */
trailing?: boolean
}
export function useDebounceFn<Fn extends (...args: any[]) => any>(
fn: Fn,
wait: MaybeRefOrGetter<number> = 1000,
options: UseDebounceFnOptions = {},
) {
const { leading = false, trailing = true } = options
let timer: ReturnType<typeof setTimeout> | undefined
let lastArgs: Parameters<Fn> | undefined
const clearTimer = () => {
if (timer !== undefined) {
clearTimeout(timer)
timer = undefined
}
}
/** Discards the pending call; calls that already ran are not undone. */
const cancel = () => {
clearTimer()
lastArgs = undefined
}
/** Runs the pending call immediately, without waiting for the delay. */
const flush = () => {
if (timer === undefined || !lastArgs) return
const pendingArgs = lastArgs
cancel()
fn(...pendingArgs)
}
const run = (...args: Parameters<Fn>) => {
const isLeading = timer === undefined
clearTimer()
lastArgs = args
if (leading && isLeading) {
lastArgs = undefined
fn(...args)
}
timer = setTimeout(() => {
timer = undefined
if (trailing && lastArgs) {
const pendingArgs = lastArgs
lastArgs = undefined
fn(...pendingArgs)
}
}, toValue(wait))
}
if (getCurrentScope()) onScopeDispose(cancel)
return { run, cancel, flush }
}API
export interface UseDebounceFnOptions {
/** Invoke once on the leading edge, before the wait starts. Default: false. */
leading?: boolean
/** Invoke on the trailing edge of the wait. Default: true. */
trailing?: boolean
}
export function useDebounceFn<Fn extends (...args: any[]) => any>(
fn: Fn,
wait: MaybeRefOrGetter<number> = 1000,
options: UseDebounceFnOptions = {},
)