JavaScript’owa funkcja debounce pozwala nam na optymalizację aplikacji pod kątem szybkości działania po stronie „frontu”, ale często też pod kątem wydajności naszego backendu…
Możemy ją wykorzystać w każdej JavaScript’owej aplikacji, nie tylko w React. Podkreślam to ponieważ przykłady na moim blogu głównie opierają się o React…

Być może większość z Was spotkała się z funkcją debounce, bo jest ona dosyć popularna. Natomiast jeżeli nie mieliście z nią styczności lub jeżeli nie mieliście możliwości użycia jej w komponencie funkcyjnym, również zostań na stronie, bo wiedza ta może się przydać. 🙂

Myślę, że komuś uratuje „życie”, a ja ze swojej strony standardowo zapraszam do praktycznego przykładu.

Instalacja:
Zaczniemy od czystegocreate-react-app a następnie zainstalujemy lodash poleceniem:
npm i --save lodash

Instalujemy lodash, aby skorzystać z wbudowanego debounce. Można oczywiście napisać własną funkcję (bez instalacji lodash), ale nie to jest celem artykułu.

Przykład

Przyjmijmy, że na naszej stronie musimy zaimplementować wyszukiwarkę, która będzie posiadała podpowiedzi pojawiające się poniżej pola wyszukiwania… Coś w stylu podpowiedzi w wyszukiwarce Google. Idąc dalej, kiedy chcemy uzyskać odpowiedź ze strony „backendu”, musimy wykonać zapytanie do API, odebrać dane, odpowiednio je przetworzyć i wyświetlić. Bez użycia optymalizacji szukając słowa „kalkulator”, wysyłalibyśmy odpowiedź po każdej literce. Łącznie byłoby to kilka żądań. W celu małej optymalizacji możemy ograniczyć wyświetlanie podpowiedzi pod pewnymi warunkami, np. podpowiedzi otrzymujemy, gdy wpiszemy minimum 3 znaki itd. Nadal jednak możemy wprowadzić małe usprawnienie…

const callApi = (query) => console.log(`API: ${query}`);

function App() {
    const [search, setSearch] = React.useState("");
    const onChange = e => {
        setSearch(e.target.value);
        callApi(e.target.value);
    }

    return (
        <div>
            Szukaj: <input type="text" onChange={onChange}/>
        </div>
    );
}

W rezultacie w naszej konsoli zobaczymy coś w stylu:

API: k
API: ka
API: kal
API: kalk
API: kalku
API: kalkul
API: kalkula
API: kalkulat
API: kalkulato
API: kalkulator

Każde wpisanie litery, uruchamia funkcję onChange, a następnie callApi, która w realnym świecie wysyłałaby request do API. Finalnie oczekujemy wyniku dla słowa „kalkulator”, a wysyłamy dodatkowe (w zasadzie niepotrzebne) zapytania… Tym samym obciążamy nie tylko backend ale też „front” użytkownika, który przerenderowuje niepotrzebnie otrzymane wyniki…

Prostym rozwiązaniem jest użycie funkcji debounce. Ma ona za zadanie wykonać funkcję po określonym czasie, od momentu ostatniego zdarzenia.
Co to oznacza w praktyce?
Dla naszego przykładu, moglibyśmy założyć, że wysyłamy request dopiero po 1 sekundzie od wpisania ostatniego znaku w wyszukiwarce.

Zmiana w kodzie jest bardzo prosta – przyjrzyjmy się fragmentowi z delayCallApi:

// import debounce
import {debounce} from "lodash";

// zmiana wewnątrz komponentu
const delayCallApi = React.useCallback(debounce(search => callApi(search), 1000), []);
const onChange = e => {
    setSearch(e.target.value);
    delayCallApi(e.target.value);
}

debounce i komponenty funkcyjne

Ważne jest, aby w przypadku komponentu funkcyjnego użyć React.useCallback(). Bez tego funkcja będzie wywoływana za każdym razem, po ustalonym przez nas opóźnieniu (zachęcam do spróbowania).
Dzieje się tak dlatego, że po ponownym rerenderze komponentu, debounce zwróci nowe „zresetowane” ustawienia i bez useCallback() to po prostu nie zadziała. Musimy zatem posłużyć się useCallback(), aby korzystać z wcześniej utworzonej funkcji i zainicjowanych ustawień. Więcej o useCallback() pisałem w innym artykule, który znajdziecie tutaj (React hooks – useCallback).

Możecie spróbować napisać ten sam kod w komponencie klasowym, korzystając z debounce. Tutaj nie mamy możliwości skorzystania z useCallback(), natomiast problem ten nie będzie występował.

Jeżeli już wprowadziliśmy zmiany, spróbujmy pobawić się wpisywanym tekstem w inputa. Jeżeli wpisywaliśmy tekst bez opóźnienia większego niż 1 sekunda, w konsoli powinniśmy otrzymać wynik:

API: kalkulator

Dzięki debounce zoptymalizowaliśmy naszą aplikację, zmniejszyliśmy ilość requestów do API do jednego końcowego, przez co również zmniejszyliśmy konieczność renderowania podpowiedzi…

To by było tyle na dziś. 🙂 W internecie można znaleźć autorskie implementacje funkcji debounce. Jeżeli ktoś ma ochotę to zapraszam do poszukiwań i zabawy.

Źródła:
Obraz: https://unsplash.com/photos/-JzHSIzNYnU
Lodash: https://lodash.com/