Jak poprawić wydajność wyszukiwania w reakcji na odrzucenie

Photo of author

By maciekx

W trakcie tworzenia funkcji wyszukiwania w React, zdarzenie `onChange` standardowo uruchamia funkcję wyszukiwania przy każdej zmianie zawartości pola tekstowego. Takie podejście może negatywnie wpływać na wydajność, zwłaszcza gdy w grę wchodzą zapytania do API lub bazy danych. Częste wywoływanie funkcji wyszukiwania może skutkować przeciążeniem serwera, prowadząc do jego awarii lub spowolnienia interfejsu użytkownika. Metodą na rozwiązanie tego problemu jest zastosowanie techniki „debounce”.

Czym jest „debounce”?

Tradycyjnie, funkcja wyszukiwania w React jest realizowana poprzez wywoływanie obsługi zdarzenia `onChange` przy każdym naciśnięciu klawisza, co demonstruje poniższy przykład:

 import { useState } from "react";

export default function Search() {
const [searchTerm, setSearchTerm] = useState("");

const handleSearch = () => {
console.log("Search for:", searchTerm);
};

const handleChange = (e) => {
setSearchTerm(e.target.value);

handleSearch();
};

return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}

Choć ten kod działa, przesyłanie zapytań do serwera przy każdej modyfikacji pola wyszukiwania może być nieefektywne. Na przykład, przy wpisywaniu „webdev”, aplikacja wysłałaby zapytania z „w”, „we”, „web” i tak dalej.

„Debounce” to technika, która pozwala na opóźnienie wykonania funkcji do momentu, gdy użytkownik przestanie wprowadzać zmiany przez określony czas. W praktyce funkcja „debounce” monitoruje aktywność użytkownika i uniemożliwia wywołanie funkcji wyszukiwania do momentu upłynięcia zdefiniowanego opóźnienia. Jeśli użytkownik nadal pisze, licznik czasu jest resetowany, a React ponownie uruchamia funkcję z nowym opóźnieniem. Proces trwa do momentu, gdy użytkownik zatrzyma wpisywanie.

Dzięki „debounce” aplikacja wysyła tylko istotne zapytania wyszukiwania, co redukuje obciążenie serwera.

Jak zaimplementować „debounce” w React?

Istnieje wiele bibliotek, które pomagają w implementacji „debounce”. Można również zaimplementować to rozwiązanie samodzielnie, wykorzystując funkcje `setTimeout` i `clearTimeout` z JavaScript.

W niniejszym artykule zostanie wykorzystana funkcja „debounce” z biblioteki lodash.

Zakładając, że masz już utworzony projekt React, stwórz nowy komponent o nazwie `Search`. W przypadku braku działającego projektu, możesz skorzystać z narzędzia do tworzenia aplikacji React.

W pliku komponentu `Search` wklej poniższy kod, który tworzy pole wyszukiwania, aktywujące funkcję obsługi przy każdej zmianie:

 import { useState } from "react";

export default function Search() {
const [searchTerm, setSearchTerm] = useState("");

const handleSearch = () => {
console.log("Search for:", searchTerm);
};

const handleChange = (e) => {
setSearchTerm(e.target.value);

handleSearch();
};

return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}

Aby zastosować „debounce” do funkcji `handleSearch`, przekaż ją do funkcji `debounce` z biblioteki lodash.

 import debounce from "lodash.debounce";
import { useState } from "react";

export default function Search() {
const [searchTerm, setSearchTerm] = useState("");

const handleSearch = () => {
console.log("Search for:", searchTerm);
};
const debouncedSearch = debounce(handleSearch, 1000);

const handleChange = (e) => {
setSearchTerm(e.target.value);

debouncedSearch();
};

return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}

W funkcji `debounce` przekazujemy funkcję, którą chcemy opóźnić (np. `handleSearch`), oraz czas opóźnienia w milisekundach (w tym przypadku 500ms).

Niestety, powyższy kod nie zadziała prawidłowo w React, ponieważ opóźni on jedynie wywołanie żądania `handleSearch`, a nie faktycznie zastosuje mechanizm „debounce”. Przyczyna tego problemu zostanie wyjaśniona w dalszej części artykułu.

„Debounce” a ponowne renderowanie

Aplikacja korzysta z kontrolowanego pola wprowadzania, co oznacza, że wartość pola jest sterowana przez stan komponentu. Przy każdej zmianie w polu, React aktualizuje stan.

W React, gdy stan ulega zmianie, komponent jest ponownie renderowany, a wraz z nim wykonuje się cała zawarta w nim logika.

W powyższym przykładzie, przy ponownym renderowaniu komponentu, React wykonuje ponownie funkcję „debounce”. Funkcja ta tworzy nowy licznik czasu i przechowuje go w pamięci, jednocześnie stary licznik czasu pozostaje aktywny. Po upływie czasu uruchamiana jest funkcja wyszukiwania, która nigdy nie jest faktycznie opóźniona, a jedynie wykonywana po 500 ms. Cykl ten powtarza się przy każdym renderowaniu – nowa funkcja tworzy nowy licznik czasu, a po czasie wywołuje funkcję wyszukiwania.

Aby mechanizm „debounce” działał poprawnie, musi zostać wywołany tylko raz. Możemy to osiągnąć, wywołując funkcję „debounce” poza komponentem, lub używając techniki zapamiętywania. Dzięki temu nawet jeśli komponent będzie renderowany ponownie, funkcja „debounce” nie zostanie ponownie wykonana.

Definiowanie funkcji „debounce” poza komponentem `Search`

Przenieś funkcję „debounce” poza komponent `Search`, jak pokazano poniżej:

 import debounce from "lodash.debounce"

const handleSearch = (searchTerm) => {
console.log("Search for:", searchTerm);
};

const debouncedSearch = debounce(handleSearch, 500);

Teraz w komponencie `Search` wywołaj `debouncedSearch`, przekazując jako argument wartość pola wyszukiwania.

 export default function Search() {
const [searchTerm, setSearchTerm] = useState("");

const handleChange = (e) => {
setSearchTerm(e.target.value);

debouncedSearch(searchTerm);
};

return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}

W ten sposób funkcja wyszukiwania zostanie wywołana dopiero po upływie czasu opóźnienia.

Zapamiętywanie funkcji „debounce”

Zapamiętywanie (ang. memoization) polega na buforowaniu wyników działania funkcji, aby móc je ponownie wykorzystać przy ponownym wywołaniu funkcji z tymi samymi argumentami.

Aby zapamiętać funkcję „debounce”, wykorzystaj hook `useMemo`.

 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("Search for:", 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="Search here..."
/>
);
}

Zwróć uwagę, że funkcja `handleSearch` została umieszczona wewnątrz hooka `useCallback`, aby mieć pewność, że React wywoła ją tylko raz. W przeciwnym razie React wykonywałby `handleSearch` przy każdym ponownym renderowaniu, co z kolei powodowałoby zmianę zależności hooka `useMemo` i ponowne wykonanie funkcji „debounce”.

Teraz funkcja „debounce” zostanie wywołana tylko wtedy, gdy zmieni się `handleSearch` lub czas opóźnienia.

Optymalizacja wyszukiwania za pomocą „debounce”

Czasami spowolnienie może prowadzić do poprawy wydajności. W kontekście obsługi funkcji wyszukiwania, szczególnie tych, które wykonują kosztowne zapytania do bazy danych lub API, najlepszym podejściem jest wykorzystanie „debounce”. Technika ta wprowadza opóźnienie przed wysłaniem zapytań do backendu.

Pomaga ona zmniejszyć liczbę zapytań wysyłanych do serwera, ponieważ zapytanie jest wysyłane dopiero po upływie czasu opóźnienia i wstrzymaniu pisania przez użytkownika. Dzięki temu serwer nie zostaje przeciążony nadmiarem zapytań, a wydajność aplikacji pozostaje na wysokim poziomie.


newsblog.pl