useEffect 去抖问题
Problems with debounce in useEffect
我有一个带有用户名输入的表单,我正在尝试验证用户名是否正在使用或不在去抖功能中。我遇到的问题是,当我输入 "user" 我的控制台看起来像
时,我的去抖动似乎不起作用
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);
debounce 函数似乎是正确的?我在 react 中调用它的方式有问题吗?
每次您的组件重新渲染时,都会创建一个 new debounced 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
,它总是 return 相同的回调(当你传入一个空数组作为第二个参数时),另外,我会传递 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;
...
我建议进行一些更改。
1) 每次进行状态更改时,都会触发渲染。每个渲染器都有自己的道具和效果。因此,您的 useEffect
会在您每次更新用户名时生成一个新的去抖功能。这可能是 useCallback hooks to keep the function instance the same between renders, or possibly useRef 的一个好案例 - 我自己坚持使用 useCallback。
2) 我会分离出单独的处理程序,而不是使用 useEffect
来触发你的去抖动——随着你的组件的增长,你最终会得到一长串的依赖关系,这不是最好的地方。
3) 你的 debounce 函数不处理参数。 (我替换为 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 和 hooks 的精彩 post。
这是解决此问题的更好方法 ;)
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;
};
}, []);
}
我有一个带有用户名输入的表单,我正在尝试验证用户名是否正在使用或不在去抖功能中。我遇到的问题是,当我输入 "user" 我的控制台看起来像
时,我的去抖动似乎不起作用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);
debounce 函数似乎是正确的?我在 react 中调用它的方式有问题吗?
每次您的组件重新渲染时,都会创建一个 new debounced 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
,它总是 return 相同的回调(当你传入一个空数组作为第二个参数时),另外,我会传递 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;
...
我建议进行一些更改。
1) 每次进行状态更改时,都会触发渲染。每个渲染器都有自己的道具和效果。因此,您的 useEffect
会在您每次更新用户名时生成一个新的去抖功能。这可能是 useCallback hooks to keep the function instance the same between renders, or possibly useRef 的一个好案例 - 我自己坚持使用 useCallback。
2) 我会分离出单独的处理程序,而不是使用 useEffect
来触发你的去抖动——随着你的组件的增长,你最终会得到一长串的依赖关系,这不是最好的地方。
3) 你的 debounce 函数不处理参数。 (我替换为 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 和 hooks 的精彩 post。
这是解决此问题的更好方法 ;)
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;
};
}, []);
}