У React, коли потрібно реалізувати функціонал пошуку, обробник події `onChange` запускає пошукову функцію кожен раз, коли користувач вносить зміни в текстове поле. Такий підхід може призвести до зниження продуктивності, особливо при виконанні запитів до API або баз даних. Часті виклики функції пошуку можуть перевантажити сервер, спричиняючи збої або зависання інтерфейсу користувача. Для розв’язання цієї проблеми використовується техніка усунення дребезгу (debouncing).
Що таке усунення дребезгу (Debouncing)?
Зазвичай, функція пошуку в React реалізується шляхом виклику обробника `onChange` при кожному натисканні клавіші, як це продемонстровано нижче:
import { useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => { console.log("Пошук:", searchTerm); };
const handleChange = (e) => { setSearchTerm(e.target.value); handleSearch(); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Пошук..." /> ); }
Хоча цей підхід працює, надсилання запиту на сервер для оновлення результатів пошуку при кожному натисканні клавіші може бути ресурсозатратним. Наприклад, при пошуку “webdev”, програма відправить запити зі значеннями “w”, “we”, “web” і так далі.
Усунення дребезгу – це техніка, яка полягає у затримці виконання функції до закінчення певного періоду часу. Функція `debounce` реагує на кожне введення тексту користувачем, але запобігає виклику обробника пошуку до тих пір, поки не пройде задана затримка. Якщо користувач продовжує вводити текст протягом періоду затримки, таймер скидається, і React знову запускає функцію з новою затримкою. Цей процес повторюється, доки користувач не припинить введення.
Чекаючи, поки користувач закінчить вводити текст, усунення дребезгу гарантує, що ваша програма виконує лише необхідні пошукові запити, тим самим зменшуючи навантаження на сервер.
Як реалізувати усунення дребезгу в React
Існує декілька бібліотек, які можна використовувати для реалізації усунення дребезгу. Ви також можете реалізувати цю техніку самостійно, використовуючи функції JavaScript `setTimeout` і `clearTimeout`.
У цьому матеріалі ми будемо використовувати функцію `debounce` з бібліотеки lodash.
Припустимо, що у вас вже є налаштований React-проект. Створіть новий компонент з назвою `Search`. Якщо ж у вас немає робочого проекту, ви можете створити новий React додаток за допомогою `create-react-app`.
У файлі компонента `Search` скопіюйте наступний код, щоб створити поле пошуку, яке викликає обробник при кожному натисканні клавіші:
import { useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => { console.log("Пошук:", searchTerm); };
const handleChange = (e) => { setSearchTerm(e.target.value); handleSearch(); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Пошук..." /> ); }
Для застосування усунення дребезгу до функції `handleSearch`, передайте її у функцію `debounce` з lodash:
import debounce from "lodash.debounce"; import { useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = () => { console.log("Пошук:", searchTerm); }; const debouncedSearch = debounce(handleSearch, 1000);
const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Пошук..." /> ); }
У функції `debounce` ви передаєте функцію, виконання якої потрібно затримати (в даному випадку `handleSearch`) та час затримки в мілісекундах (1000 мс).
Хоча наведений вище код мав би затримувати виклик запиту `handleSearch` до закінчення введення користувачем, насправді це не працює належним чином у React. У наступному розділі ми пояснимо чому.
Усунення дребезгу і повторні рендери
Ця програма використовує контрольований вхід. Це означає, що значення стану керує значенням вхідних даних; кожен раз, коли користувач вводить текст в поле пошуку, React оновлює стан.
У React, коли змінюється значення стану, React повторно рендерить компонент і виконує всі функції, що знаходяться в ньому.
У наведеному вище компоненті пошуку, коли компонент повторно рендериться, React знову виконує функцію `debounce`. Функція створює новий таймер, який відслідковує затримку, а старий таймер зберігається в пам’яті. По закінченню часу він запускає функцію пошуку. Це означає, що функція пошуку ніколи не зупиняється, а лише затримується на 500 мс. Цей цикл повторюється під час кожного рендерингу – функція створює новий таймер, старий таймер закінчується, а потім викликає функцію пошуку.
Щоб функція `debounce` працювала належним чином, її потрібно викликати лише один раз. Ви можете цього досягти, викликаючи функцію `debounce` за межами компонента або використовуючи техніку мемоізації. Таким чином, навіть якщо компонент повторно рендериться, React не буде виконувати її знову.
Визначення функції Debounce поза компонентом пошуку
Перемістіть функцію усунення дребезгу за межі компонента пошуку, як показано нижче:
import debounce from "lodash.debounce"const handleSearch = (searchTerm) => { console.log("Пошук:", searchTerm); };
const debouncedSearch = debounce(handleSearch, 500);
Тепер, у компоненті пошуку, викличте `debouncedSearch` та передайте пошуковий термін:
export default function Search() { const [searchTerm, setSearchTerm] = useState("");const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(searchTerm); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Пошук..." /> ); }
Функція пошуку буде викликана лише після закінчення періоду затримки.
Мемоізація функції Debounce
Мемоізація — це кешування результатів функції та їх повторне використання, коли функція викликається з тими ж самими аргументами.
Для мемоізації функції `debounce` використовуйте хук `useMemo`.
import debounce from "lodash.debounce"; import { useCallback, useMemo, useState } from "react";export default function Search() { const [searchTerm, setSearchTerm] = useState("");
const handleSearch = useCallback((searchTerm) => { console.log("Пошук:", searchTerm); }, []);
const debouncedSearch = useMemo(() => { return debounce(handleSearch, 500); }, [handleSearch]);
const handleChange = (e) => { setSearchTerm(e.target.value); debouncedSearch(searchTerm); };
return ( <input onChange={handleChange} value={searchTerm} placeholder="Пошук..." /> ); }
Зверніть увагу, що функція `handleSearch` також обгорнута в хук `useCallback`, щоб переконатися, що React виконує її лише один раз. Без хука `useCallback` React виконував би функцію `handleSearch` при кожному повторному рендерингу, що робило б залежність хука `useMemo` нестабільною, а це, в свою чергу, знову викликало б функцію `debounce`.
Тепер React викличе функцію `debounce` лише тоді, коли зміниться функція `handleSearch` або час затримки.
Оптимізація пошуку за допомогою Debounce
Іноді затримка може бути корисна для продуктивності. При виконанні пошукових завдань, особливо з дорогими запитами до бази даних або API, використання функції усунення дребезгу є дуже доцільним. Ця функція вводить затримку перед відправленням серверних запитів.
Це допомагає зменшити кількість запитів, відправлених на сервер, оскільки запит надсилається лише після того, як затримка закінчилася і користувач припинив введення. Таким чином, сервер не перевантажується надмірною кількістю запитів, а продуктивність залишається високою.