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 подходит для более сложных случаев, когда нужно дибаунсить не только значение, но и вызовы функций с аргументами.

    Часто задаваемые вопросы