Ein Suchfeld, das bei jedem Tastendruck eine Anfrage abschickt, belastet die API und lässt die Ergebnisse flackern. Debouncing löst das: warte, bis der Nutzer eine Pause macht, und handle dann einmal. Hier ist eine kleine, wiederverwendbare Eingabe mit Debounce, zu der ich in Svelte 5 immer wieder greife.
Warum der Aufwand? Es reduziert Anfragen von einer pro Tastendruck auf eine pro Pause, senkt damit Serverlast und Bandbreite, beseitigt das Flackern halb getippter Ergebnisse, und die Komponente passt mit konfigurierbarer Verzögerung in jedes Formular.
Der debounce-Helfer
Ein Closure hält einen Timer. Jeder Aufruf löscht den laufenden Timer und plant
einen neuen, sodass die umschlossene Funktion erst nach delay Millisekunden
Stille läuft.
export function debounce(func, delay) { let timeoutId;
return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); };}DebouncedInput.svelte
Die Komponente hält ihren eigenen value-State für die schnelle Bindung bei
jedem Tastendruck und gibt den debounced Wert über $bindable() nach
außen. Der $effect ruft update bei jeder Änderung von value auf; update
schreibt erst dann in debouncedValue, wenn das Tippen 300ms ruht.
<script lang="ts"> import { debounce } from '$lib';
let { debouncedValue = $bindable(), initialValue, ...props } = $props();
const update = debounce((v: string) => (debouncedValue = v), 300);
let value = $state(initialValue); $effect(() => update(value));</script>
<input type="text" bind:value {...props} />Verwendung
Binde deinen Suchbegriff an debouncedValue. Der $effect im Elternteil läuft
nur erneut, wenn sich der debounced Begriff ändert, also feuert der Fetch
einmal pro Pause statt einmal pro Tastendruck.
<script lang="ts"> import DebouncedInput from './DebouncedInput.svelte';
let searchTerm = $state(''); let results = $state([]); let loading = $state(false);
$effect(async () => { if (!searchTerm.trim()) { results = []; return; }
loading = true; try { const response = await fetch(`/api/search?q=${encodeURIComponent(searchTerm)}`); results = await response.json(); } catch (error) { console.error('Search failed:', error); results = []; } finally { loading = false; } });</script>
<DebouncedInput bind:debouncedValue={searchTerm} placeholder="Suchen..." class="w-full px-3 py-2 border rounded"/>
{#if loading} <p>Suche läuft...</p>{:else if results.length > 0} <ul> {#each results as result} <li>{result.title}</li> {/each} </ul>{/if}Das ist das ganze Muster: ein Closure, ein Stück lokaler State, eine bindbare Ausgabe. Setze es vor ein beliebiges Suchfeld, und dein Backend wird nicht mehr zugespammt.