Debouncer: практический пример использования замыкания

от автора

Что такое дебаунсер?

Дебаунсер — это функция-обертка, которая ограничивает число выполнений переданной в нее функции, некоторым промежутком времени.

Практическое применение

Предположим, при вводе текста в инпут мы хотим отправлять запрос на сервер, чтобы получить выпадающий список вариантов под введенное значение.

По умолчанию, запрос будет уходить при вводе очередной буквы, но чтобы не грузить сервер запросами и не повторять рендер страницы с одинаковыми по значению данными, мы хотим обернуть запрос в дебаунсер с некоторым ограничением по времени.

Таким образом, запрос не будет совершаться при вводе каждой следующей буквы, а будет ограничен некоторым промежутком времени.

Как реализовать дебаунсер?

Чтобы правильно реализовать дебаунсер, нам нужно чтобы каждый следующий вызов целевой функции “знал” о своем предыдущем вызове, и относительно этих данных дебаунсер решал — выполнять функцию или откладывать.

Для дебаунсера “эти данные” — это идентификатор таймаута.

Так причем тут замыкания?

А при том, что для того, чтобы последующий вызов мог получать доступ к идентификатору таймаута, при этом ограничив этот идентификатор для внешнего изменения удобно использовать замыкание.

Что такое замыкание?

Замыкание — это функция, которая инкапсулирует и возвращает функцию с ее окружением.

Совсем упрощая, можно сказать, что окружение — или лексическое окружение — это блок кода внутри фигурных скобок.

Переменные же, объявленные в этом окружении (в частности в замыкании), во первых, будут недоступны извне этого окружения. Это свойство языка javascript — доступ к переменным окружения есть только у него самого и у его дочерних окружений, при условии, что доступ запрашивается после объявления.

Второе свойство — окружение функции не удаляется из памяти сразу после выполнения самой функции. Таким образом, переменные объявленные в замыкании могут быть “совместно” использованы между разными вызовами возвращаемой замыканием функции.

Давайте теперь уже посмотрим, как оно будет на практике.

Для начала объявим функцию-замыкание debounce, принимающая функцию-колбек и лимит ее выполнения. Она будет содержать переменную хранящую айдишник таймаута каждого последующего вызова:

export const debounce = (callback, interval) => {   let prevTimeoutId;    return (...args) => {     prevTimeoutId = setTimeout(() => {       callback(args);     }, limit);   } }

Аргументы возвращаемой функции мы передаем в колбек. Обратите внимание — в каком порядке передаются аргументы в замыкание: …args — это те аргументы, которые функция получит на последнем (то есть втором) вызове — например это может быть объект события, если, скажем, дебаунсер передается как обработчик события.

Дальше логика такая: на следующем вызове функции нам нужно удалить из памяти предыдущий вызов — это можно сделать по айди таймаута с помощью функции clearTimeout. Затем нам нужно объявить новый таймаут, сохранить его айдишник и вернуть новую функцию:

const debounce = (callback, interval = 0) => {   let prevTimeoutId;     return (...args) => {     clearTimeout(timeoutId);     prevTimeoutId = setTimeout(() => callback(...args), interval);   } }

Теперь, если мы захотим теперь использовать наш дебаунсер на инпуте, то выглядеть это будет так:

document.querySelector('input').addEventListener(   'input',   debounce(ev => console.log(ev.target.value), 1000) );

И при вводе символов в инпут, выводится в консоль будут только значения, введенные в интервале одной секунды.

Добавим реактивности!

В случае, если мы хотим использовать дебаунсер внутри реакт-компонента, то его нужно преобразовать в кастомный реакт-хук, чтобы идентификатор предыдущего таймаута и возвращаемая функция не исчезали из памяти. Делается это при помощи хуков useRef и useCallback соответственно:

const useDebounce = (callback, interval = 0) => {   const prevTimeoutIdRef = React.useRef();    return React.useCallback(     (...args) => {       clearTimeout(prevTimeoutIdRef.current);       prevTimeoutIdRef.current = setTimeout(() => {         clearTimeout(prevTimeoutIdRef.current);         callback(...args);       }, interval);     },     [callback, interval]   ); };

И если мы дальше планируем использовать его в useEffect, то во избежании ошибок сначала нужно инициализировать useDebounce с колбеком и интервалом в переменную, а уже потом вызывать эту переменную в useEffect и передавать в нее нужные аргументы.

На этом собственно все.

Надеюсь вам понравилось это увлекательное путешествие в мир дебаунсеров и замыканий.

Рекомендую следующую статью по замыканиям:

https://learn.javascript.ru/closure

спасибо


ссылка на оригинал статьи https://habr.com/ru/articles/743666/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *