Где использовал Debounce?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование 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 раз
Лучшие практики
-
Выбирайте правильную задержку:
- Поиск: 300-500ms
- Resize: 200-300ms
- Автосохранение: 1000-2000ms
-
Используйте с useCallback в React:
const debounced = useCallback( debounce(fn, delay), [] ); -
Очищайте таймауты при размонтировании компонента
-
Предоставляйте визуальную обратную связь:
- Индикатор загрузки
- Статус сохранения
- Сообщение об ошибке
Итог
Debounce используется для:
- Оптимизации сетевых запросов
- Снижения нагрузки на процессор
- Улучшения пользовательского опыта
- Экономии ресурсов
Это один из самых полезных паттернов в фронтенд-разработке, который должен знать каждый разработчик.