函数组件里如何正确使用防抖(_.debounce)函数
yaofly2012 opened this issue · comments
yq commented
背景
假设有个Search
组件,用户在输入过程中,需要异步调用接口获取用户搜索的结果。为了更有效的调用接口我们会使用防抖函数处理接口调用频率。
实现(V1)
import { useEffect, useState } from "react";
import { debounce } from "lodash";
export default function App() {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
const logger = debounce(value => {
setTimeout(() => {
console.log(`Letter Count ${(value || "").length}`);
}, 300);
}, 240);
useEffect(() => {
logger(value);
}, [value]);
return (
<div className="App">
<p>输入过程中查看console输出</p>
<input onChange={handleChange} value={value} />
</div>
);
}
效果&问题
在输入过程中,好像接口调用只是被delay
了(类似setTimeout
效果),并没有防抖效果。
原因分析
每次render
是都是创建一个新的debounced
函数,并没有启到防抖效果。更像是执行一个setTimeout
延时调用接口,调用接口次数并没有减少。
实现(V2)
import { useEffect, useState, useRef } from "react";
import { debounce } from "lodash";
export default function App() {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
const loggerRef = useRef(
debounce((value) => {
setTimeout(() => {
console.log(`Letter Count ${(value || "").length}`);
}, 300);
}, 240)
);
useEffect(() => {
loggerRef.current(value);
}, [value]);
return (
<div className="App">
<p>输入过程中查看console输出</p>
<input onChange={handleChange} value={value} />
</div>
);
}
效果&问题
利用useRef
保存debounced
函数,这样就启到防抖效果了。但是这样写还是存在一些问题:
- 每次
render
时,虽然loggerRef.current
的值不变,但是还是创建了debounced`函数; - 在
useEffect
的清理函数里把被延时执行的操作cancel
掉,可以在组件卸载是执行下cancel
操作。
改进实现
可以自定义个hook:useDebounce
/**
* 自定义hooks专门处理
*/
function useDebounce(func, wait, options) {
const debounced = useRef();
// “懒”计算
if (debounced.current === void 0) {
debounced.current = debounce(func, wait, options);
}
useEffect(() => {
return () => {
debounced.current.cancel(); // 组件卸载时执行下`cancel`
};
}, []);
return debounced;
}
引用useDebounce
import { useEffect, useState, useRef } from "react";
import { debounce } from "lodash";
export default function App() {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value);
};
const loggerRef = useDebounce((value) => {
setTimeout(() => {
console.log(`Letter Count ${(value || "").length}`);
}, 300);
}, 220);
useEffect(() => {
const debounced = loggerRef.current;
debounced(value);
// return () => debounced.cancel(); // 这里没有必要调用`cance`操作,debounced内部会清理delay的setTimeout
}, [value]);
return (
<div className="App">
<p>输入过程中查看console输出</p>
<input onChange={handleChange} value={value} />
</div>
);
}