Debouncing retrasa una acción hasta que el usuario deja de escribir durante un tiempo determinado. Sin él, un input de búsqueda dispara una llamada a la API en cada tecla. Escribir "vue router" envía 10 peticiones, la mayoría inútiles porque el usuario no ha terminado de escribir.
Enfoque inline
La forma más simple: observar el valor del input y retrasar la actualización de la query real:
<script setup lang="ts">
import { ref, watch } from 'vue'
const searchQuery = ref('')
const debouncedQuery = ref('')
let timeout: ReturnType<typeof setTimeout>
watch(searchQuery, (newVal) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
debouncedQuery.value = newVal
}, 300)
})
</script>
<template>
<input v-model="searchQuery" placeholder="Buscar..." />
<SearchResults :query="debouncedQuery" />
</template>El usuario escribe en searchQuery (feedback instantáneo). Después de 300ms sin escribir, debouncedQuery se actualiza y dispara la búsqueda real. Cada nueva tecla reinicia el temporizador.
Extraer un composable reutilizable
Si haces debounce en múltiples lugares, extrae el patrón:
import { ref, watch, type Ref } from 'vue'
export function useDebouncedRef<T>(source: Ref<T>, delay = 300): Ref<T> {
const debounced = ref(source.value) as Ref<T>
let timeout: ReturnType<typeof setTimeout>
watch(source, (val) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
debounced.value = val
}, delay)
})
return debounced
}<script setup lang="ts">
import { ref } from 'vue'
import { useDebouncedRef } from '@/composables/useDebouncedRef'
const searchQuery = ref('')
const debouncedQuery = useDebouncedRef(searchQuery, 300)
</script>
<template>
<input v-model="searchQuery" placeholder="Buscar..." />
<SearchResults :query="debouncedQuery" />
</template>Usando VueUse
VueUse tiene refDebounced (misma idea, probado en producción) y useDebounceFn (hace debounce a cualquier función):
import { ref } from 'vue'
import { refDebounced } from '@vueuse/core'
const searchQuery = ref('')
const debouncedQuery = refDebounced(searchQuery, 300)Debounce vs throttle
Debounce espera hasta que la actividad se detiene (dispara una vez después del último evento). Throttle dispara a intervalos regulares durante la actividad (como máximo una vez por intervalo). Para inputs de búsqueda, debounce suele ser la opción correcta porque quieres esperar hasta que el usuario termine de escribir. Para handlers de scroll o resize, throttle suele ser mejor porque quieres actualizaciones periódicas mientras el evento está ocurriendo.
Ver también: ¿Qué es VueUse? · ¿Cómo construirías un composable para data fetching? · ¿Qué es un composable?
Referencias
- Composables - Vue.js docs
- refDebounced - VueUse docs
- useDebounceFn - VueUse docs