Debounce в React: сравнение двух подходов для живого поиска
При реализации живого поиска (search by text) в React разработчики часто сталкиваются с необходимостью ограничить частоту вызовов API или фильтрации данных. Это называется debounce (антидребезг). В статье разберём два популярных подхода: использование отдельной функции debounce и создание кастомного хука useDebounce. Сравним их корректность, производительность и удобство сопровождения.
Подход 1: функция debounce с useCallback
Первый вариант - классическая функция debounce, которая возвращает новую функцию с задержкой. В компоненте она оборачивается в useCallback для мемоизации. Пример реализации:
export const debounce = (cb: Function, delay: number) => { let timer: any; return (...args: any[]) => { clearTimeout(timer); timer = setTimeout(() => cb(...args), delay); };};Использование в компоненте:
const debouncedSearchText = useCallback(debounce((value: string) => handleFilter(value), 300), []);useEffect(() => { setError(''); if (searchText.length > 2 || !searchText) { debouncedSearchText(searchText); }}, [searchText]);Плюсы и минусы
- Плюс: функция debounce универсальна и может использоваться вне React.
- Минус: необходимо правильно передавать пустой массив зависимостей в useCallback, иначе debounce будет создаваться заново при каждом рендере.
- Минус: при изменении
handleFilter(например, если она зависит от состояния) debounce не обновится, что может привести к багам.
Подход 2: кастомный хук useDebounce
Второй способ - создать собственный хук, который откладывает обновление значения на заданное количество миллисекунд. Пример:
export const useDebounce = (dbValue: string, delay: number) => { const [value, setValue] = React.useState(dbValue); useEffect(() => { let timer = setTimeout(() => { setValue(dbValue); }, delay); return () => clearTimeout(timer); }, [dbValue]); return value;};Использование в компоненте:
const debouncedText = useDebounce(searchText, 300);useEffect(() => { handleFilter(debouncedText);}, [debouncedText]);Плюсы и минусы
- Плюс: чистый, реактивный подход - задержка применяется к значению, а не к функции.
- Плюс: легко читается и тестируется.
- Минус: создаёт дополнительный рендер при каждом изменении
dbValue(сначала с новым значением, потом с задержанным).
Какой подход правильнее?
Оба подхода рабочие, но кастомный хук useDebounce считается более идиоматичным для React. Он не требует ручного управления зависимостями и лучше вписывается в компонентную архитектуру. Однако первый вариант (с функцией debounce) полезен, когда нужно дибаунсить не только состояние, но и вызовы функций (например, обработчики событий).
Типичные ошибки и как их избежать
В первом подходе часто забывают указать пустой массив зависимостей в useCallback, что приводит к сбросу таймера при каждом рендере. Во втором - важно не забыть очистить таймер в useEffect (возвращаемая функция clearTimeout).
Также стоит помнить, что при использовании useDebounce значение debouncedText обновляется с задержкой, поэтому если пользователь быстро вводит текст, запросы будут отправляться только после паузы. Это снижает нагрузку на сервер и улучшает UX.
Заключение
Для большинства сценариев живого поиска в React рекомендуется использовать кастомный хук useDebounce. Он проще, надёжнее и легче поддерживается. Функция debounce с useCallback подходит для более сложных случаев, когда нужно дибаунсить не только значение, но и вызовы функций с аргументами.