I React-applikasjoner, når man implementerer søkefunksjonalitet, utløses ofte en `onChange`-hendelse som igjen kaller en søkefunksjon hver gang brukeren taster inn noe i søkefeltet. Denne direkte tilnærmingen kan raskt føre til ytelsesproblemer, spesielt ved hyppige API-kall eller komplekse databaseoperasjoner. Stadige anrop til søkefunksjonen kan legge unødvendig press på webserveren, som igjen kan forårsake uforutsigbar respons eller treghet i brukergrensesnittet. En løsning på dette problemet er å bruke «debouncing».
Hva er «debouncing»?
Normalt sett implementeres søkefunksjonaliteten i React ved å kalle en funksjon som håndterer `onChange`-hendelsen ved hvert tastetrykk, slik det vises i eksempelet under:
import { useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => { console.log("Søk etter:", searchTerm); };
const handleChange = (e) => { setSearchTerm(e.target.value); handleSearch(); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Søk her..." /> ); }
Selv om denne koden fungerer, kan det bli kostbart å kontakte serveren for å oppdatere søkeresultatene ved hvert eneste tastetrykk. For eksempel, ved et søk etter «webutvikling», vil applikasjonen potensielt sende en forespørsel for «w», «we», «web», osv.
Debouncing er en teknikk som forsinker utførelsen av en gitt funksjon til en bestemt tidsperiode har passert. Debounce-funksjonen registrerer hver gang brukeren skriver, og hindrer et direkte kall til søkefunksjonen før tidsforsinkelsen er utløpt. Hvis brukeren fortsetter å skrive innenfor denne tidsperioden, vil timeren tilbakestilles, og React kaller funksjonen på nytt for den nye forsinkelsen. Denne prosessen repeteres inntil brukeren slutter å skrive.
Ved å vente på at brukerne skal ta en pause i skrivingen, sikrer debouncing at applikasjonen bare utfører de nødvendige søkeforespørslene, og dermed reduseres serverbelastningen betraktelig.
Hvordan implementere debouncing for søk i React?
Det finnes flere biblioteker som kan brukes til å implementere debouncing. Det er også mulig å implementere det fra bunnen av ved hjelp av JavaScripts `setTimeout` og `clearTimeout` funksjoner.
I denne artikkelen vil vi bruke debounce-funksjonen fra lodash-biblioteket.
Forutsatt at du har et React-prosjekt klart, kan du lage en ny komponent som heter «Søk». Hvis du ikke har et fungerende prosjekt, kan du opprette et nytt med `create-react-app`.
I søkekomponentfilen kopierer du følgende kode for å lage et søkeinndatafelt som kaller en behandlerfunksjon for hvert tastetrykk.
import { useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => { console.log("Søk etter:", searchTerm); };
const handleChange = (e) => { setSearchTerm(e.target.value); handleSearch(); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Søk her..." /> ); }
For å bruke debounce-funksjonen på `handleSearch`, send den til `debounce`-funksjonen fra lodash.
import debounce from "lodash.debounce"; import { useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => { console.log("Søk etter:", searchTerm); }; const debouncedSearch = debounce(handleSearch, 1000);
const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Søk her..." /> ); }
I `debounce`-funksjonen sender du inn funksjonen du ønsker å forsinke, som er `handleSearch`-funksjonen, og forsinkelsestiden i millisekunder, i dette tilfellet 1000ms.
Selv om koden ovenfor burde forsinke anropet til `handleSearch` til etter at brukeren har stoppet skrivingen, vil den ikke fungere som forventet i React. Dette forklares i neste seksjon.
Debouncing og gjengivelser
Denne applikasjonen bruker en «kontrollert inngang». Det vil si at tilstandens verdi styrer verdien av inndatafeltet. Hver gang en bruker skriver i søkefeltet, oppdaterer React tilstanden.
I React, når en tilstandsverdi endres, vil React gjengi komponenten og utføre alle funksjonene den inneholder.
I søkekomponenten ovenfor vil React utføre debounce-funksjonen hver gang komponenten gjengis. Dette fører til at en ny timer lages for å holde styr på forsinkelsen, mens den gamle timeren forblir i minnet. Når tiden utløper, vil den utløse søkefunksjonen. Dette betyr at søkefunksjonen ikke blir «debounced», men kun forsinket med 1000ms. Denne syklusen gjentas for hver gjengivelse, der funksjonen lager en ny timer, den gamle utløper, og til slutt kalles søkefunksjonen.
For at debounce-funksjonen skal fungere som tiltenkt, må den bare kalles en gang. Dette kan gjøres ved å kalle debounce-funksjonen utenfor komponenten eller ved å bruke memoriseringsteknikker. På denne måten, selv om komponenten gjengis, vil ikke React kjøre den på nytt.
Definere debounce-funksjonen utenfor søkekomponenten
Flytt debounce-funksjonen utenfor søkekomponenten slik det vises nedenfor:
import debounce from "lodash.debounce"const handleSearch = (searchTerm) => { console.log("Søk etter:", searchTerm); };
const debouncedSearch = debounce(handleSearch, 500);
I søkekomponenten kalles nå `debouncedSearch`, og søkeordet sendes med som parameter.
export default function Search() { const [searchTerm, setSearchTerm] = useState("");const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(searchTerm); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Søk her..." /> ); }
Nå vil søkefunksjonen først bli kalt etter at forsinkelsesperioden er utløpt.
Memoizere debounce-funksjonen
Memoisering refererer til å lagre resultatet av en funksjon og gjenbruke det når funksjonen kalles med de samme argumentene.
For å memoizere debounce-funksjonen brukes `useMemo`-hooken.
import debounce from "lodash.debounce"; import { useCallback, useMemo, useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = useCallback((searchTerm) => { console.log("Søk etter:", searchTerm); }, []);
const debouncedSearch = useMemo(() => { return debounce(handleSearch, 500); }, [handleSearch]);
const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(searchTerm); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Søk her..." /> ); }
Merk at `handleSearch`-funksjonen også er pakket inn i en `useCallback`-hook for å sørge for at React bare kaller den én gang. Uten `useCallback`-hooken ville React ha utført `handleSearch`-funksjonen ved hver gjengivelse, som igjen ville ført til at avhengighetene til `useMemo`-hooken endret seg, noe som igjen ville kalt `debounce`-funksjonen.
Nå vil React kun kalle `debounce`-funksjonen dersom `handleSearch`-funksjonen eller forsinkelsestiden endres.
Optimalisere søk med Debounce
Noen ganger kan det lønne seg å være litt tregere for å oppnå bedre ytelse. Når du håndterer søkeoppgaver, spesielt med tunge database- eller API-anrop, er det fornuftig å bruke en debounce-funksjon. Denne funksjonen introduserer en forsinkelse før backend-forespørsler sendes.
Dette bidrar til å redusere antall forespørsler som sendes til serveren, siden den kun sender forespørselen etter at forsinkelsen er utløpt og brukeren har stoppet å skrive. På denne måten unngår man å overbelaste serveren med for mange forespørsler, og ytelsen forblir effektiv.