我有一个带有用户名输入的表单,我正在尝试验证用户名是否在去抖动功能中使用。我遇到的问题是我的去抖动似乎不起作用,因为当我输入"用户"时,我的控制台看起来像
u
us
use
user
这是我的去抖功能
export function debounce(func, wait, immediate) {
var timeout;
return () => {
var context = this, args = arguments;
var later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
这是我在我的 React 组件中调用它的方式
import React, { useEffect } from 'react'
// verify username
useEffect(() => {
if(state.username !== "") {
verify();
}
}, [state.username])
const verify = debounce(() => {
console.log(state.username)
}, 1000);
去抖功能似乎是正确的?我在反应中如何调用它有问题吗?
每次组件重新渲染时,都会创建一个新的去抖verify
函数,这意味着在useEffect
内部,您实际上是在调用不同的函数,这违背了去抖的目的。
就像你在做这样的事情:
const debounced1 = debounce(() => { console.log(state.username) }, 1000);
debounced1();
const debounced2 = debounce(() => { console.log(state.username) }, 1000);
debounced2();
const debounced3 = debounce(() => { console.log(state.username) }, 1000);
debounced3();
而不是你真正想要的:
const debounced = debounce(() => { console.log(state.username) }, 1000);
debounced();
debounced();
debounced();
解决此问题的一种方法是使用useCallback
它将始终返回相同的回调(当您传入空数组作为第二个参数时(。另外,我会将username
传递给此函数,而不是访问内部的状态(否则您将访问过时的状态(:
import { useCallback } from "react";
const App => () {
const [username, setUsername] = useState("");
useEffect(() => {
if (username !== "") {
verify(username);
}
}, [username]);
const verify = useCallback(
debounce(name => {
console.log(name);
}, 200),
[]
);
return <input onChange={e => setUsername(e.target.value)} />;
}
此外,您还需要稍微更新您的去抖动函数,因为它没有将参数正确传递给去抖动函数。
function debounce(func, wait, immediate) {
var timeout;
return (...args) => { <--- needs to use this `args` instead of the ones belonging to the enclosing scope
var context = this;
...
演示
注意:您将看到一个关于useCallback
如何期望内联函数的 ESLint 警告,您可以通过使用useMemo
来解决此问题,知道useCallback(fn, deps)
等效于useMemo(() => fn, deps)
:
const verify = useMemo(
() => debounce(name => {
console.log(name);
}, 200),
[]
);
export function useLazyEffect(effect: EffectCallback, deps: DependencyList = [], wait = 300) {
const cleanUp = useRef<void | (() => void)>();
const effectRef = useRef<EffectCallback>();
const updatedEffect = useCallback(effect, deps);
effectRef.current = updatedEffect;
const lazyEffect = useCallback(
_.debounce(() => {
cleanUp.current = effectRef.current?.();
}, wait),
[],
);
useEffect(lazyEffect, deps);
useEffect(() => {
return () => {
cleanUp.current instanceof Function ? cleanUp.current() : undefined;
};
}, []);
}
我建议做一些更改。
1( 每次进行状态更改时,都会触发渲染。每个渲染都有自己的道具和效果。因此,每次更新用户名时,您的useEffect
都会生成一个新的去抖动功能。这是useCallback钩子的一个很好的例子,可以在渲染之间保持函数实例相同,或者可能是useRef-我自己坚持使用useCallback。
2(我会分离出单个处理程序,而不是使用useEffect
来触发您的去抖动 - 随着组件的增长,您最终会拥有一长串依赖项,这不是最佳位置。
3(你的去抖函数不处理参数。(我用lodash.debouce替换了,但你可以调试你的实现(
4(我认为您仍然想在按键上更新状态,但每x秒才运行一次您谴责的功能
例:
import React, { useState, useCallback } from "react";
import "./styles.css";
import debounce from "lodash.debounce";
export default function App() {
const [username, setUsername] = useState('');
const verify = useCallback(
debounce(username => {
console.log(`processing ${username}`);
}, 1000),
[]
);
const handleUsernameChange = event => {
setUsername(event.target.value);
verify(event.target.value);
};
return (
<div className="App">
<h1>Debounce</h1>
<input type="text" value={username} onChange={handleUsernameChange} />
</div>
);
}
演示
我强烈建议阅读这篇关于useEffect和钩子的好文章。
一个简单的debounce
功能,带有useEffect
、useState
钩
import {useState, useEffect} from 'react';
export default function DebounceInput(props) {
const [timeoutId, setTimeoutId] = useState();
useEffect(() => {
return () => {
clearTimeout(timeoutId);
};
}, [timeoutId]);
function inputHandler(...args) {
setTimeoutId(
setTimeout(() => {
getInputText(...args);
}, 250)
);
}
function getInputText(e) {
console.log(e.target.value || "Hello World!!!");
}
return (
<>
<input type="text" onKeyDown={inputHandler} />
</>
);
}
我希望这运作良好。下面我附上了香草js代码以debounce
function debounce(cb, delay) {
let timerId;
return (...args) => {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => {
cb(...args);
}, delay);
};
}
function getInputText(e){
console.log(e.target.value);
}
const input = document.querySelector('input');
input.addEventListener('keydown',debounce(getInputText,500));
这是普通 JavaScript 中的一个自定义钩子,它将实现去抖useEffect
:
export const useDebounce = (func, timeout=100) => {
let timer;
let deferred = () => {
clearTimeout(timer);
timer = setTimeout(func, timeout);
};
const ref = useRef(deferred);
return ref.current;
};
export const useDebouncedEffect = (func, deps=[], timeout=100) => {
useEffect(useDebounce(func, timeout), deps);
}
对于您的示例,您可以像这样使用它:
useDebouncedEffect(() => {
if(state.username !== "") {
console.log(state.username);
}
}, [state.username])