Организация задержки запроса при вводе данных в input
Стоит задача организовать задержку запроса при вводе текста в input для минимизации отправки запросов на сервер. Написал такой код:
const App = () => {
const [text, setText] = useState("");
const request = (text) => {
console.log("пошол зарос на сервер");
};
const debaunce = (fn, timeoutMs) => {
return function perform(...args) {
const previousCall = this.lastCall;
this.lastCall = Date.now();
console.log(this)
if (previousCall && this.lastCall - previousCall <= timeoutMs) {
clearTimeout(this.lastCallTimer);
}
this.lastCallTimer = setTimeout(() => fn(...args), timeoutMs)
};
};
const debaunceRequest = debaunce(request, 1000);
const handleInput = (e) => {
const value = e.target.value;
setText(value);
debaunceRequest(value);
};
return (
<>
<div className="wrapper1">
<div className="wrapper2">
<input
placeholder="введи текст"
onChange={(e) => handleInput(e)}
className="inputField field"
value={text}
/>
</div>
</div>
</>
);
};
Функция debaunce организует ту самую задержку. Но в codesandbox https://codesandbox.io/s/gracious-buck-wjhbys?file=/src/App.js:58-1065
this ссылается на объект window и поэтому все работает. Но в реальном проекте мне кажется использовать Window в таких целях не правильно. Пробовал написать так:
const debaunce = <T extends never>(fn: T, timeoutMs: number) =>
function perform(...args) {
let lastCall = null;
let lastCallTimer: any = null;
const previousCall = lastCall;
console.log(lastCall, lastCallTimer);
lastCall = Date.now();
if (previousCall && lastCall - previousCall <= timeoutMs) {
clearTimeout(lastCallTimer);
}
lastCallTimer = setTimeout(() => fn(...args), timeoutMs);
};
Но в таком случае переменным lastCallTimer и lastCall значения не присваиваются. Подскажите прав ли я по поводу использования window в проекте? Почему не идёт присвоение значений в переменные. Оговорюсь что я знаю про множество других решений с задержкой времени. И с признательностью рассмотрю предложенные варианты. Но всё таки хочется разобраться с этим примером. Заранее благодарю за помощь
Ответы (2 шт):
Подскажите прав ли я по поводу использования window в проекте?
Думаю что по возможности этого нужно избегать.
Почему не идёт присвоение значений в переменные.
Присвоение "идет"... Но в твоем варианте нет ничего постоянного, все переопределяется при рендере. Кроме window.
Но всё таки хочется разобраться с этим примером.
Как вариант, можно useRef-ом создать "постоянный" объект (аналог window) и в нем хранить твои переменные.
const App = () => {
const [text, setText] = React.useState("");
const obj = React.useRef({});
const request = (text) => {
console.log("пошол зарос на сервер");
};
const debaunce = (fn, timeoutMs) => {
return function perform(...args) {
const previousCall = obj.current.lastCall;
obj.current.lastCall = Date.now();
if (previousCall && obj.current.lastCall - previousCall <= timeoutMs) {
clearTimeout(obj.current.lastCallTimer);
}
obj.current.lastCallTimer = setTimeout(() => fn(...args), timeoutMs)
};
};
const debaunceRequest = debaunce(request, 1000);
const handleInput = (e) => {
const value = e.target.value;
setText(value);
debaunceRequest(value);
};
return (
<div className="wrapper1">
<div className="wrapper2">
<input
placeholder="введи текст"
onChange={(e) => handleInput(e)}
className="inputField field"
value={text}
/>
</div>
</div>
);
};
const domContainer = document.querySelector('#like_button_container');
const root = ReactDOM.createRoot(domContainer);
root.render(<App />);
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<div id="like_button_container"></div>
Оговорюсь что я знаю про множество других решений с задержкой времени. И с признательностью рассмотрю предложенные варианты.
Вот такой вариант можно использовать...
const request = (text) => {
console.log("пошол зарос на сервер");
};
const App = () => {
const [text, setText] = React.useState(null);
React.useEffect(_ => {
if (text === null) return
const v = setTimeout(request, 1000)
return _ => clearTimeout(v)
}, [text])
const handleInput = (e) => {
const value = e.target.value;
setText(value);
};
return (
<div className="wrapper1">
<div className="wrapper2">
<input
placeholder="введи текст"
onChange={(e) => handleInput(e)}
className="inputField field"
value={text || ''}
/>
</div>
</div>
);
};
const domContainer = document.querySelector('#like_button_container');
const root = ReactDOM.createRoot(domContainer);
root.render(<App />);
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<div id="like_button_container"></div>
Всем спасибо за ответы. Я написал hook с использованием useRef. Получилось так:
function useDebounce(fn: any, timeoutMs: number): Function {
// в ссылках хранится id таймера и время предидущего вызова функции
const lastCallTimerRef = useRef<number | null>(null);
const lastCallRef = useRef<number | null>(null);
return function perform(...args: Array<any>) {
// при повторном вызове функции текущее время прошлого вызова становится прошлым временем для текущего вызова
const previousCall = lastCallRef.current;
lastCallRef.current = Date.now();
if (previousCall && lastCallRef.current - previousCall <= timeoutMs) {
clearTimeout(lastCallTimerRef.current as number);
}
lastCallTimerRef.current = window.setTimeout(() => fn(...args), timeoutMs);
};
}