U React-u, kada se implementira funkcija pretrage, `onChange` rukovalac poziva funkciju pretrage svaki put kada korisnik unosi tekst u polje za unos. Ovaj pristup može dovesti do problema sa performansama, posebno ako se šalju API pozivi ili upiti bazi podataka. Učestali pozivi funkcije pretrage mogu preopteretiti server, što može rezultirati padovima ili korisničkim interfejsom koji ne reaguje. Debouncing rešava ovaj izazov.
Šta je Debouncing?
Uobičajeno je da se funkcija pretrage u React-u implementira tako da se `onChange` rukovalac poziva pri svakom pritisku na taster, kao što je prikazano u sledećem primeru:
import { useState } from "react"; export default function Search() { const [searchTerm, setSearchTerm] = useState(""); const handleSearch = () => { console.log("Pretraga za:", searchTerm); }; const handleChange = (e) => { setSearchTerm(e.target.value); handleSearch(); }; return ( <input onChange={handleChange} value={searchTerm} placeholder="Pretraži ovde..." /> ); }
Iako ovo funkcioniše, slanje zahteva na backend za ažuriranje rezultata pretrage pri svakom pritisku tastera može biti skupo. Na primer, ako korisnik traži „webdev“, aplikacija bi poslala zahtev backendu sa vrednostima „w“, „we“, „web“ i tako dalje.
Debouncing je tehnika koja funkcioniše tako što odlaže izvršavanje funkcije dok ne prođe određeni vremenski interval. Funkcija debounce detektuje svaki put kada korisnik kuca i sprečava poziv rukovaocu pretrage sve dok kašnjenje ne istekne. Ako korisnik nastavi da kuca tokom perioda odlaganja, tajmer se resetuje i React ponovo poziva funkciju za novo odlaganje. Ovaj proces se nastavlja sve dok korisnik ne prestane sa unosom teksta.
Čekanjem da korisnik pauzira kucanje, debouncing osigurava da vaša aplikacija šalje samo neophodne zahteve za pretragu, smanjujući opterećenje servera.
Kako primeniti Debouncing u React-u?
Postoji nekoliko biblioteka koje se mogu koristiti za implementaciju debouncing-a. Takođe, moguće je ručno implementirati debouncing koristeći JavaScript funkcije `setTimeout` i `clearTimeout`.
Ovaj članak koristi funkciju `debounce` iz `lodash` biblioteke.
Pretpostavlja se da već imate React projekat. Ako ne, kreirajte novi projekat koristeći `create-react-app`. Nakon toga, kreirajte novu komponentu pod nazivom `Pretraga`.
U datoteci komponente `Search`, kopirajte sledeći kod za kreiranje polja za unos teksta koje poziva rukovaoca pri svakom pritisku na taster.
import { useState } from "react"; export default function Search() { const [searchTerm, setSearchTerm] = useState(""); const handleSearch = () => { console.log("Pretraga za:", searchTerm); }; const handleChange = (e) => { setSearchTerm(e.target.value); handleSearch(); }; return ( <input onChange={handleChange} value={searchTerm} placeholder="Pretraži ovde..." /> ); }
Da bi se funkcija `handleSearch` odložila, prosledite je funkciji za uklanjanje dupliranja iz `lodash`-a.
import debounce from "lodash.debounce"; import { useState } from "react"; export default function Search() { const [searchTerm, setSearchTerm] = useState(""); const handleSearch = () => { console.log("Pretraga za:", searchTerm); }; const debouncedSearch = debounce(handleSearch, 1000); const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(); }; return ( <input onChange={handleChange} value={searchTerm} placeholder="Pretraži ovde..." /> ); }
U funkciji `debounce`, prosleđuje se funkcija čije se izvršavanje želi odložiti, u ovom slučaju `handleSearch`, kao i vreme odlaganja u milisekundama, npr. 500 ms.
Iako bi navedeni kod trebao da odloži poziv `handleSearch` dok korisnik ne prestane sa kucanjem, to ne funkcioniše u React-u. Objasnićemo zašto u narednom odeljku.
Debouncing i Ponovno Renderovanje
Ova aplikacija koristi kontrolisani unos. To znači da vrednost stanja kontroliše vrednost unosa; svaki put kada korisnik unese tekst u polje za pretragu, React ažurira stanje.
U React-u, kada se vrednost stanja promeni, React ponovo renderuje komponentu i izvršava sve funkcije unutar nje.
U navedenoj komponenti pretrage, kada se komponenta ponovo renderuje, React izvršava funkciju `debounce`. Ova funkcija kreira novi tajmer koji prati kašnjenje, a stari tajmer ostaje u memoriji. Kada vreme istekne, funkcija pretrage se pokreće. To znači da se funkcija pretrage nikada ne odlaže, već kasni 500 ms. Ovaj ciklus se ponavlja pri svakom renderovanju – funkcija kreira novi tajmer, stari tajmer ističe, a zatim poziva funkciju pretrage.
Da bi funkcija `debounce` ispravno radila, mora se pozvati samo jednom. To se može postići pozivanjem funkcije `debounce` izvan komponente ili korišćenjem tehnike memoizacije. Na taj način, čak i ako se komponenta ponovo renderuje, React je neće ponovo izvršiti.
Definisanje funkcije `debounce` izvan komponente pretrage
Premestite funkciju `debounce` izvan komponente `Search` kao što je prikazano:
import debounce from "lodash.debounce" const handleSearch = (searchTerm) => { console.log("Pretraga za:", searchTerm); }; const debouncedSearch = debounce(handleSearch, 500);
Sada, u komponenti `Search`, pozovite `debouncedSearch` i prosledite termin za pretragu.
export default function Search() { const [searchTerm, setSearchTerm] = useState(""); const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(searchTerm); }; return ( <input onChange={handleChange} value={searchTerm} placeholder="Pretraži ovde..." /> ); }
Funkcija pretrage će biti pozvana tek nakon što istekne period odlaganja.
Memoizacija funkcije `debounce`
Memoizacija se odnosi na keširanje rezultata funkcije i njihovo ponovno korišćenje kada se funkcija pozove sa istim argumentima.
Za memoizaciju funkcije odlaganja, koristi se `useMemo` hook.
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("Pretraga za:", 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="Pretraži ovde..." /> ); }
Primeti se da je funkcija `handleSearch` takođe obuhvaćena `useCallback` hook-om kako bi se osiguralo da je React poziva samo jednom. Bez `useCallback` hook-a, React bi izvršavao funkciju `handleSearch` pri svakom ponovnom renderovanju, što bi uzrokovalo da zavisnost u `useMemo` hook-u se menja, a samim tim i da se poziva funkcija `debounce`.
Sada će React pozvati funkciju `debounce` samo ako se promeni funkcija `handleSearch` ili vreme odlaganja.
Optimizacija Pretrage pomoću Debounce-a
Ponekad je usporavanje bolje za performanse. Kada se radi sa zadacima pretrage, posebno sa zahtevnim pozivima baze podataka ili API-ja, najbolje je koristiti funkciju `debounce`. Ova funkcija uvodi odlaganje pre slanja zahteva na backend.
Ona pomaže u smanjenju broja zahteva upućenih serveru, jer šalje zahtev tek nakon što prođe odlaganje i korisnik pauzira sa unosom teksta. Na ovaj način, server nije preopterećen sa previše zahteva, a performanse ostaju efikasne.