Как можно правильно реализовать функцию debounce в React?

Существует два подхода к реализации дебаунса для живого поиска (search by text) в React:

<b>1.</b> <br/>
<i>Создание функции для дебаунса</i> <br/>
<pre><code class="javascript">export const debounce=(cb:Function,delay:number)=&gt; {
    let timer:any
    return (...args:any[])=&gt; {
        clearTimeout(timer);
        timer = setTimeout(()=&gt;cb(...args),delay);
    }
}</code></pre> <br/>
<i>Применение в компоненте</i> <br/>
<pre><code class="javascript">const [searchText, setSearchText] = React.useState("");
    const [error, setError] = React.useState("");
    const [filterResults, setFilterResults] = React.useState([]);

    const handleFilter = async (searchText: string) =&gt; {
        const res = await filterByName(searchText);
        if (res.error) {
            setError(res.error);
        } else {
            setFilterResults(res.results);
        }
    }

    const debouncedSearchText = useCallback(debounce((value: string) =&gt; handleFilter(value), 300), [])
    
useEffect(() =&gt; {
        setError("")
        if (searchText.length &gt; 2 || !searchText) {
            debouncedSearchText(searchText);
        }
    }, [searchText]);</code></pre> <br/>
<br/>

<b>2.</b> <br/>
<i>Создание хука для дебаунса и его использование</i> <br/>
Хук: <br/>
<pre><code class="javascript">export const useDebounce = (dbValue: string, delay: number) =&gt; {
    const [value, setValue] = React.useState(dbValue);

    useEffect(() =&gt; {
        let timer = setTimeout(() =&gt; {
            setValue(dbValue)
        }, delay);

        return () =&gt; {
            clearTimeout(timer)
        };
    }, [dbValue])

    return value
}</code></pre> <br/>
<br/>
<pre><code class="javascript">const [searchText, setSearchText]...
  • 16 апреля 2025 г. 14:05
Ответы на вопрос 2
Оба подхода, которые вы описали, являются корректными методами реализации функции дебаунса в React, и каждый из них имеет свои преимущества. Давайте рассмотрим каждый из них более подробно и приведем полный пример использования.

### 1. Создание функции для дебаунса

В этом методе мы создаем отдельную функцию для дебаунса и используем ее в компоненте. Отправляем обновленный текст через `debouncedSearchText`, когда происходит изменение состояния `searchText`.

```javascript
// debounce.js
export const debounce = (cb: Function, delay: number) => {
    let timer: NodeJS.Timeout;
    return (...args: any[]) => {
        clearTimeout(timer);
        timer = setTimeout(() => cb(...args), delay);
    }
};

// Component.js
import React, { useState, useEffect, useCallback } from 'react';
import { debounce } from './debounce';
import { filterByName } from './api'; // Предположим, что это ваша функция асинхронного запроса

const SearchComponent = () => {
    const [searchText, setSearchText] = useState("");
    const [error, setError] = useState("");
    const [filterResults, setFilterResults] = useState([]);

    const handleFilter = async (searchText: string) => {
        const res = await filterByName(searchText);
        if (res.error) {
            setError(res.error);
        } else {
            setFilterResults(res.results);
        }
    };

    const debouncedSearchText = useCallback(debounce((value: string) => handleFilter(value), 300), []);

    useEffect(() => {
        setError("");
        if (searchText.length > 2 || !searchText) {
            debouncedSearchText(searchText);
        }
    }, [searchText]);

    return (
        <div>
            <input
                type="text"
                value={searchText}
                onChange={(e) => setSearchText(e.target.value)}
                placeholder="Search..."
            />
            {error && <div>{error}</div>}
            <ul>
                {filterResults.map(result => (
                    <li key={result.id}>{result.name}</li> // пример отображения результатов
                ))}
            </ul>
        </div>
    );
};
```

### 2. Создание хука для дебаунса

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

```javascript
// useDebounce.js
import { useEffect, useState } from 'react';

export const useDebounce = (value: string, delay: number) => {
    const [debouncedValue, setDebouncedValue] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);

        return () => {
            clearTimeout(handler);
        };
    }, [value, delay]);

    return debouncedValue;
};

// Component.js
import React, { useState, useEffect } from 'react';
import { useDebounce } from './useDebounce';
import { filterByName } from './api';

const SearchComponent = () => {
    const [searchText, setSearchText] = useState("");
    const [error, setError] = useState("");
    const [filterResults, setFilterResults] = useState([]);

    // Используем наш хук для дебаунса
    const debouncedSearchText = useDebounce(searchText, 300);

    useEffect(() => {
        const search = async () => {
            if (debouncedSearchText.length > 2 || !debouncedSearchText) {
                setError("");
                const res = await filterByName(debouncedSearchText);
                if (res.error) {
                    setError(res.error);
                } else {
                    setFilterResults(res.results);
                }
            }
        };
        search();
    }, [debouncedSearchText]);

    return (
        <div>
            <input
                type="text"
                value={searchText}
                onChange={(e) => setSearchText(e.target.value)}
                placeholder="Search..."
            />
            {error && <div>{error}</div>}
            <ul>
                {filterResults.map(result => (
                    <li key={result.id}>{result.name}</li>
                ))}
            </ul>
        </div>
    );
};
```

### Заключение

Оба подхода имеют свои применения — если вам нужно использовать дебаунс в нескольких местах в вашем приложении, создание пользовательского хука будет хорошим выбором. В то время как прямое использование функции дебаунса
Оба варианта рабочие, но. <br/> - Хук не используешь в циклах и условиях. <br/> - Хук не используешь вне реакта. <br/> <br/> - Хук более многословен, для 4 инпутов понадобится 4 вызова хука, 4 состояния и 4 эффекта для работы с каждым по отдельности - это весомый минус, а для useCallback с дебаунсом всего 4: <br/> <pre><code class="jsx">const debouncedSearchText = useCallback(debounce((value) =&gt; console.log(value), 300), []);
const debouncedSearchText = useCallback(debounce((value) =&gt; console.log(value), 300), []);
const debouncedSearchText = useCallback(debounce((value) =&gt; console.log(value), 300), []);
const debouncedSearchText = useCallback(debounce((value) =&gt; console.log(value), 300), []);</code></pre> <br/> <b>VS</b> <br/> <pre><code class="jsx">const [inputValue, setInputValue] = useState('');
  const debouncedValue = useDebounce(inputValue, 300);

  useEffect(() =&gt; {
    console.log(debouncedValue);
  }, [debouncedValue]);

const [inputValue, setInputValue] = useState('');
  const debouncedValue = useDebounce(inputValue, 300);

  useEffect(() =&gt; {
    console.log(debouncedValue);
  }, [debouncedValue]);

const [inputValue, setInputValue] = useState('');
  const debouncedValue = useDebounce(inputValue, 300);

  useEffect(() =&gt; {
    console.log(debouncedValue);
  }, [debouncedValue]);

const [inputValue, setInputValue] = useState('');
  const debouncedValue = useDebounce(inputValue, 300);

  useEffect(() =&gt; {
    console.log(debouncedValue);
  }, [debouncedValue]);</code></pre> <br/> <br/> Поэтому я считаю, что первый вариант намного предпочтительнее.
Похожие вопросы