Что такое дебаунсер?
Дебаунсер - это функция-обертка, которая ограничивает число выполнений переданной в нее функции, некоторым промежутком времени.
Практическое применение
Предположим, при вводе текста в инпут мы хотим отправлять запрос на сервер, чтобы получить выпадающий список вариантов под введенное значение.
По умолчанию, запрос будет уходить при вводе очередной буквы, но чтобы не грузить сервер запросами и не повторять рендер страницы с одинаковыми по значению данными, мы хотим обернуть запрос в дебаунсер с некоторым ограничением по времени.
Таким образом, запрос не будет совершаться при вводе каждой следующей буквы, а будет ограничен некоторым промежутком времени.
Как реализовать дебаунсер?
Чтобы правильно реализовать дебаунсер, нам нужно чтобы каждый следующий вызов целевой функции “знал” о своем предыдущем вызове, и относительно этих данных дебаунсер решал - выполнять функцию или откладывать.
Для дебаунсера “эти данные” - это идентификатор таймаута.
Так причем тут замыкания?
А при том, что для того, чтобы последующий вызов мог получать доступ к идентификатору таймаута, при этом ограничив этот идентификатор для внешнего изменения удобно использовать замыкание.
Что такое замыкание?
Замыкание - это функция, которая инкапсулирует и возвращает функцию с ее окружением.
Совсем упрощая, можно сказать, что окружение - или лексическое окружение - это блок кода внутри фигурных скобок.
Переменные же, объявленные в этом окружении (в частности в замыкании), во первых, будут недоступны извне этого окружения. Это свойство языка 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 и передавать в нее нужные аргументы.
На этом собственно все.
Надеюсь вам понравилось это увлекательное путешествие в мир дебаунсеров и замыканий.
Рекомендую следующую статью по замыканиям:
Автор:
karmacan