Допустим мы имеем очень абстрактный компонент, который имеет поле ввода текста, кнопку отправки этого текста на сервер, и сообщение о состоянии отправки данных на сервер
Пример кода
// функция иммитации запроса на сервер
function fakeFetch(val: string) {
return new Promise<string>(function (resolve) {
setTimeout(function () {
resolve(`Сервер получил сообщение: ${val}`);
}, 1000);
});
}
// самый простой пример компонента
function App() {
const [val, setVal] = useState('');
const [response, setResponse] = useState('');
const inputHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setVal(e.target.value);
setResponse('');
};
const submitHandler = async () => {
setVal('');
setResponse('отправка ...');
const resp = await fakeFetch(val);
setResponse(resp);
};
return (
<>
<input type="text" value={val} onInput={inputHandler} />
<button onClick={submitHandler}>Отправить</button>
<p>{response}</p>
</>
);
}
Теперь перед нами встала задача заменить поведение: отправлять данные не по кнопке, а автоматически. Однако мы не может просто так повесить функцию в обработчик inputHandler(), мало того что он будет неоправданно часто отправлять запросы на сервер, так еще и будет сбивать значение val
. Но если мы добавить функцию debounce()
, то она решит эти вопросы.
Функция debounce()
debounce() - это функция-обертка, которая ограничивает число выполнений переданной в нее функции, некоторым промежутком времени. Если точнее, то, пока промежуток времени не прошел, то зарегистрированная функция не будет исполнятся, а пока она не исполнилась, новый вызов функции будет замещать старый, исключая повторное выполнение.
пример:
function debounce(func, delay: number) {
let timeoutId: number;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
Как правильно использовать в React компоненте
function App() {
const [val, setVal] = useState('');
const [response, setResponse] = useState('');
const inputHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setVal(e.target.value);
setResponse('');
debounceHandler();
};
const submitHandler = useCallback(async () => {
setResponse('отправка ...');
const resp = await fakeFetch(val);
setVal('');
setResponse(resp);
}, []);
const debounceHandler = useMemo(() => {
return debounce(submitHandler, 800);
}, [submitHandler]);
return (
<>
<input type="text" value={val} onInput={inputHandler} />
{/* <button onClick={submitHandler}>Отправить</button> */}
<p>{response}</p>
</>
);
}
При изменении стейта функциональный компонент вызывает свою функцию каждый раз, и без мемоизации мы бы получали каждый раз новый обработчик debounceHandler()
и submitHandler()
. Но для того чтобы все работало нам необходимо передать одну функцию в debounce один раз!
Поэтому мемоизируем submitHandler() через useCallback
(линтер обычно потребует указать submitHandler
как зависимость debounceHandler
) и саму функцию debounceHandler()
в useMemo
, (т.к debounce() возвращает функцию)