Jak poprawić wydajność wyszukiwania w Reakcie dzięki debouncingowi?
W React, podczas implementacji funkcji wyszukiwania, program obsługi onChange wywołuje funkcję wyszukiwania za każdym razem, gdy użytkownik wpisuje dane w polu wprowadzania. Takie podejście może powodować problemy z wydajnością, zwłaszcza w przypadku wywołań API lub zapytań do bazy danych. Częste wywoływanie funkcji wyszukiwania może przeciążyć serwer WWW, prowadząc do awarii lub braku reakcji interfejsu użytkownika. Debouncing rozwiązuje ten problem.
Co to jest debouncing?
W typowej implementacji funkcji wyszukiwania w React, zazwyczaj wywołuje się funkcję obsługi onChange przy każdym naciśnięciu klawisza, jak pokazano poniżej:
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);
// Calls search function
handleSearch();
};
return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}
Aby zoptymalizować wydajność, ważne jest, aby wziąć pod uwagę, że częste aktualizacje wyników wyszukiwania poprzez połączenia z zapleczem mogą stać się kosztowne podczas wpisywania słów kluczowych, takich jak “webdev”. W tym scenariuszu aplikacja wysyła żądania do zaplecza za każdym razem, gdy wprowadzany jest nowy znak, w tym znaki takie jak “w”, “we”, “web” itp.
Debouncing polega na odroczeniu wykonania funkcji do momentu upłynięcia określonego czasu od jej ostatniego wywołania. W tym scenariuszu funkcja debounced zostanie uruchomiona tylko wtedy, gdy użytkownik przestanie pisać po określonym czasie. Jeśli w wyznaczonym okresie nastąpi dalsze wprowadzanie danych, licznik czasu zresetuje się i zainicjuje kolejną rundę przetwarzania. Cykl ten powtarza się tak długo, jak długo nie zostaną wykonane żadne dodatkowe naciśnięcia klawiszy, kończąc się, gdy użytkownik przestanie pisać.
Debouncing pozwala aplikacji na efektywne zarządzanie żądaniami serwera poprzez opóźnianie ich do momentu, aż użytkownik przestanie wprowadzać dane, minimalizując w ten sposób niepotrzebne wyszukiwania i odciążając serwer.
How to Debounce Search in React
W celu włączenia funkcji debouncingu do aplikacji internetowej, istnieje wiele istniejących wcześniej bibliotek, które zapewniają taką możliwość. Alternatywnie, można zdecydować się na skonstruowanie algorytmu debouncera od podstaw przy użyciu metod JavaScript setTimeout
i clearTimeout
.
Niniejszy artykuł wykorzystuje funkcjonalność debouncingu dostarczaną przez bibliotekę Lodash, która jest popularnym narzędziem JavaScript oferującym szeroki zakres funkcji do manipulacji danymi i optymalizacji operacji.
Aby rozpocząć proces tworzenia funkcji wyszukiwania w ramach istniejącego projektu React lub poprzez utworzenie nowego, konieczne jest najpierw utworzenie nowego komponentu o nazwie “Search”. Można to osiągnąć poprzez wykorzystanie istniejącej aplikacji React lub użycie narzędzia “create React app” do wygenerowania w pełni funkcjonalnego środowiska React.
W pliku komponentu Search można wykorzystać dostarczony fragment kodu do wygenerowania pola wejściowego wyszukiwania, które będzie wykonywać wyznaczoną funkcję zwrotną przy każdym naciśnięciu klawisza.
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);
// Calls search function
handleSearch();
};
return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}
Aby opóźnić wykonanie funkcji handleSearch
za pomocą funkcji debounce
dostarczonej przez bibliotekę Lodash, można przekazać wspomnianą funkcję jako argument do metody debounce
. Zapewni to, że funkcja zostanie wykonana tylko raz przez określony czas, po czym wszelkie kolejne wywołania w tym przedziale czasowym będą ignorowane aż do wygaśnięcia okresu oczekiwania.
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);
// Calls search function
debouncedSearch();
};
return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}
W implementacji mechanizmu debouncingu wprowadzamy niestandardową wersję funkcji handleSearch
, przyjmując ją jako argument wraz z określonym interwałem czasowym, a mianowicie 500 milisekund, który służy jako próg wyzwalający opóźnione wykonanie wspomnianej funkcji.
Oczekuje się, że powyższy kod odroczy wykonanie funkcji handleSearch
do momentu, aż użytkownik przestanie wprowadzać dane; jednak ta funkcjonalność nie działa zgodnie z przeznaczeniem w ramach frameworka React. Przyczyny tej rozbieżności zostaną omówione w dalszej części artykułu.
Debouncing and Rerenders
Niniejsza aplikacja wykorzystuje regulowane dane wejściowe, w których status quo systemu określa zawartość dostarczaną jako dane wyjściowe za pośrednictwem paska wyszukiwania. Z każdym naciśnięciem klawisza wprowadzonym przez użytkownika w tym konkretnym interfejsie, React odpowiednio dołącza modyfikacje do konfiguracji stanu.
React to popularna biblioteka JavaScript używana do tworzenia interfejsów użytkownika. Za każdym razem, gdy wartość stanu w komponencie React ulega zmianie, cały komponent jest ponownie renderowany, co wiąże się z ponownym wykonaniem wszystkich jego funkcji od początku do końca. Pozwala to na dynamiczną aktualizację interfejsu bez konieczności pełnego odświeżania strony.
Wspomniany wcześniej komponent wyszukiwania podlega ponownemu renderowaniu, podczas którego React aktywuje mechanizm debouncingu. Tworzony jest nowy timer do monitorowania opóźnienia, podczas gdy istniejący pozostaje aktywny w pamięci systemu. Po wygaśnięciu tego ostatniego, funkcja wyszukiwania zostaje uruchomiona po odroczeniu o 500 milisekund. W rezultacie sekwencja ta powtarza się przy każdym renderowaniu, ponieważ nowy licznik czasu ma pierwszeństwo przed poprzednim, a starszy licznik czasu kończy swój okres zakończenia przed wielokrotnym wywołaniem funkcji wyszukiwania.
Aby upewnić się, że funkcja debounced działa zgodnie z przeznaczeniem w komponencie React, ważne jest, aby ograniczyć jej wykonanie do pojedynczej instancji.Jedno z podejść do osiągnięcia tego celu polega na wykorzystaniu techniki memoizacji w połączeniu z funkcją debouncing. W ten sposób, nawet gdy komponent zostanie poddany ponownemu renderowaniu, funkcja odrzucająca nie będzie wykonywana wielokrotnie.
Definiowanie funkcji debounce poza komponentem Search
Rozważ przeniesienie mechanizmu odroczonego wykonania, takiego jak debounce
, do pozycji poza komponentem Search
, jak pokazano w poniższym przykładzie:
import debounce from "lodash.debounce"
const handleSearch = (searchTerm) => {
console.log("Search for:", searchTerm);
};
const debouncedSearch = debounce(handleSearch, 500);
W bieżącej iteracji komponentu Search naszego projektu, zaimplementujemy debouncowaną wersję funkcji search poprzez wywołanie metody
debouncedSearch i przekazanie jej zapytania jako argumentu.
export default function Search() {
const [searchTerm, setSearchTerm] = useState("");
const handleChange = (e) => {
setSearchTerm(e.target.value);
// Calls search function
debouncedSearch(searchTerm);
};
return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}
Funkcjonalność funkcji wyszukiwania jest uzależniona od upływu wcześniej zdefiniowanego przedziału czasu, w którym zostanie ona wywołana.
Memoizing the Debounce Function
Memoizacja polega na przechowywaniu danych wyjściowych generowanych przez określoną funkcję po otrzymaniu określonych danych wejściowych, aby można je było łatwo pobrać i wykorzystać w przypadku ponownego wywołania funkcji z identycznymi parametrami. Technika ta jest stosowana jako strategia optymalizacji w celu zminimalizowania kosztów obliczeniowych, szczególnie w przypadku powtarzających się obliczeń.
Aby efektywnie buforować i odraczać wykonanie funkcji z debouncem, wykorzystując mechanizm memoizacji Reacta, należy użyć haka useMemo w połączeniu z odpowiednią strategią implementacji dla pożądanego efektu debouncingu.
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);
// Calls search function
debouncedSearch(searchTerm);
};
return (
<input
onChange={handleChange}
value={searchTerm}
placeholder="Search here..."
/>
);
}
Należy zauważyć, że zamknęliśmy funkcję handleSearch
w haku useCallback
, aby zagwarantować, że React wywoła tę funkcję tylko raz. Gdyby nie hak useCallback
, React wielokrotnie wykonywałby funkcję handleSearch
podczas każdego procesu renderowania, powodując zmiany w zależnościach haka useMemo
i w konsekwencji wyzwalając funkcję debounce
.
React powinien wywoływać funkcję debounce wyłącznie w przypadkach, w których funkcja handleSearch
lub okres opóźnienia ulegają zmianie.
Zoptymalizuj wyszukiwanie za pomocą debounce
Czasami umiarkowane tempo może prowadzić do zwiększenia produktywności. W sytuacjach związanych z kosztownymi zapytaniami do bazy danych lub API podczas operacji wyszukiwania, rozsądne staje się wykorzystanie funkcji debounce. Funkcja ta nakłada czasową pauzę przed przekazaniem żądań do zaplecza.
Wdrożenie mechanizmu opóźnionego ładowania obrazów na stronach internetowych może znacznie zmniejszyć obciążenie serwerów poprzez ograniczenie częstotliwości wysyłanych do nich żądań. Konfigurując system tak, aby czekał, aż użytkownik wstrzyma wprowadzanie danych przed wysłaniem żądania obrazu, minimalizujemy liczbę żądań, które są przetwarzane jednocześnie. W rezultacie podejście to utrzymuje optymalną wydajność serwera, jednocześnie zapobiegając wyczerpaniu zasobów lub opóźnieniom w czasie odpowiedzi.