← Назад к вопросам

Где использовал Debounce?

1.7 Middle🔥 212 комментариев
#React#Оптимизация и производительность

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование Debounce в веб-разработке

Debounce - это техника, которая задерживает выполнение функции до момента, когда событие перестанет происходить в течение определённого времени. Это один из самых полезных паттернов оптимизации в фронтенде.

Что такое Debounce

Debounce откладывает вызов функции и «перезагружает» таймер каждый раз при новом событии. Функция вызывается только после того, как события перестанут поступать на протяжении установленного времени.

// Простая реализация debounce
function debounce(func, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func(...args);
    }, delay);
  };
}

// Пример использования
const logMessage = debounce((message) => {
  console.log(message);
}, 1000);

logMessage("Первый вызов");
logMessage("Второй вызов");
logMessage("Третий вызов");
// После 1 секунды: "Третий вызов"

Основные сценарии использования

1. Поиск и автодополнение

Это самый распространённый случай - отправка запроса на сервер при вводе пользователя:

// React пример
function SearchUsers() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const searchUsers = useCallback(
    debounce(async (searchQuery) => {
      if (!searchQuery.trim()) {
        setResults([]);
        return;
      }

      setLoading(true);
      try {
        const response = await fetch(
          `https://api.example.com/users/search?q=${searchQuery}`
        );
        const data = await response.json();
        setResults(data);
      } finally {
        setLoading(false);
      }
    }, 500),
    []
  );

  const handleInputChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    searchUsers(value);
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleInputChange}
        placeholder="Поиск пользователей..."
      />
      {loading && <p>Загрузка...</p>}
      <ul>
        {results.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Зачем нужен debounce:

  • Без debounce: при вводе "javascript" сервер получит 10 запросов
  • С debounce: сервер получит 1 запрос после паузы в вводе
  • Экономит трафик и нагрузку на сервер

2. Изменение размера окна (Resize events)

Обработка события resize может вызывать множество срабатываний в секунду:

function ResponsiveLayout() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  const handleResize = useCallback(
    debounce(() => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }, 250),
    []
  );

  useEffect(() => {
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, [handleResize]);

  return (
    <div>
      <p>Размер окна: {windowSize.width} x {windowSize.height}</p>
    </div>
  );
}

Проблема без debounce:

  • Resize срабатывает 60+ раз в секунду
  • Вычисления выполняются неэффективно
  • Браузер может зависнуть

3. Сохранение черновиков и автосохранение

Сохранение форм при вводе пользователя:

function Editor() {
  const [content, setContent] = useState("");
  const [saved, setSaved] = useState(true);

  const saveContent = useCallback(
    debounce(async (text) => {
      try {
        await fetch("https://api.example.com/save-draft", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ content: text })
        });
        setSaved(true);
      } catch (error) {
        console.error("Save failed:", error);
      }
    }, 1500),
    []
  );

  const handleChange = (e) => {
    const text = e.target.value;
    setContent(text);
    setSaved(false);
    saveContent(text);
  };

  return (
    <div>
      <textarea
        value={content}
        onChange={handleChange}
        placeholder="Пишите здесь..."
      />
      {!saved && <p>Сохранение...</p>}
      {saved && <p>Сохранено</p>}
    </div>
  );
}

4. Фильтрация данных в таблицах

Фильтрование большого набора данных при вводе:

function DataTable() {
  const [filter, setFilter] = useState("");
  const [filteredData, setFilteredData] = useState([]);
  const allData = []; // большой массив данных

  const filterData = useCallback(
    debounce((query) => {
      if (!query.trim()) {
        setFilteredData(allData);
        return;
      }

      // Дорогостоящая операция фильтрации
      const filtered = allData.filter(item =>
        item.name.toLowerCase().includes(query.toLowerCase()) ||
        item.email.toLowerCase().includes(query.toLowerCase())
      );
      setFilteredData(filtered);
    }, 300),
    []
  );

  const handleFilterChange = (e) => {
    const value = e.target.value;
    setFilter(value);
    filterData(value);
  };

  return (
    <div>
      <input
        type="text"
        value={filter}
        onChange={handleFilterChange}
        placeholder="Фильтр..."
      />
      <table>
        <tbody>
          {filteredData.map(item => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

5. Валидация форм в реальном времени

function RegistrationForm() {
  const [email, setEmail] = useState("");
  const [error, setError] = useState("");
  const [validating, setValidating] = useState(false);

  const validateEmail = useCallback(
    debounce(async (emailValue) => {
      if (!emailValue) {
        setError("");
        return;
      }

      setValidating(true);
      try {
        const response = await fetch(
          `https://api.example.com/check-email?email=${emailValue}`
        );
        const data = await response.json();

        if (data.exists) {
          setError("Email уже зарегистрирован");
        } else if (!data.isValid) {
          setError("Некорректный формат email");
        } else {
          setError("");
        }
      } finally {
        setValidating(false);
      }
    }, 500),
    []
  );

  const handleEmailChange = (e) => {
    const value = e.target.value;
    setEmail(value);
    validateEmail(value);
  };

  return (
    <div>
      <input
        type="email"
        value={email}
        onChange={handleEmailChange}
        placeholder="Email"
      />
      {validating && <p>Проверка...</p>}
      {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
  );
}

6. Обработка скролла

function InfiniteScroll() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);

  const loadMore = useCallback(
    debounce(async () => {
      const response = await fetch(
        `https://api.example.com/items?page=${page}`
      );
      const newItems = await response.json();
      setItems(prev => [...prev, ...newItems]);
      setPage(prev => prev + 1);
    }, 300),
    [page]
  );

  const handleScroll = () => {
    const scrollTop = window.scrollY;
    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;

    if (scrollTop + windowHeight >= documentHeight - 500) {
      loadMore();
    }
  };

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, [handleScroll]);

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>{item.title}</div>
      ))}
    </div>
  );
}

Debounce vs Throttle

Часто путают debounce и throttle:

// Debounce - выполнить ровно один раз ПОСЛЕ паузы
const debouncedSearch = debounce(search, 500);
// События: 1ms, 2ms, 3ms, ... 500ms - выполнить

// Throttle - выполнить с определённой частотой
const throttledScroll = throttle(handleScroll, 100);
// События: каждые 100ms выполнять максимум 1 раз

Лучшие практики

  1. Выбирайте правильную задержку:

    • Поиск: 300-500ms
    • Resize: 200-300ms
    • Автосохранение: 1000-2000ms
  2. Используйте с useCallback в React:

    const debounced = useCallback(
      debounce(fn, delay),
      []
    );
    
  3. Очищайте таймауты при размонтировании компонента

  4. Предоставляйте визуальную обратную связь:

    • Индикатор загрузки
    • Статус сохранения
    • Сообщение об ошибке

Итог

Debounce используется для:

  • Оптимизации сетевых запросов
  • Снижения нагрузки на процессор
  • Улучшения пользовательского опыта
  • Экономии ресурсов

Это один из самых полезных паттернов в фронтенд-разработке, который должен знать каждый разработчик.

Где использовал Debounce? | PrepBro