通过debounce函数来减少某些操作的触发频次,已经成为了司空见惯的优化方式。
debounce 是如何减少函数调用的?
我们先写一个简单的debounce函数
function debounce(fn, ms) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
}, ms);
}
}
复制代码
原理其实就是利用闭包缓存了timer
变量,通过clearTimeout
去取消一些多余的操作。
但是什么叫如何在hook里面正确使用debounce函数
先写一个日常常见的’有问题’的debounce使用方式:
function App() {
const fn = debounce(() => console.log(1), 500);
return (
<div>
<input onChange={fn} />
</div>
);
}
复制代码
这个debounce函数可以生效吗?
.
..
…
答案是可以的,那为什么可以呢,我们一步一步来。
先看测试结果
我们连续输入了几个1之后,
只会有一个1个1
的输出,这证明了我们debounce函数是生效的。
需求 – pm 需要有一个按钮可以一键清空 input内的值
简单,绑定value就可以了。
function App() {
const [value, setValue] = useState('');
const fn = debounce(() => console.log(1), 500);
return (
<div>
<input
value={value}
onChange={(event) => {
const _v = event.target.value;
setValue(_v);
fn();
}}
/>
<br />
<button onClick={() => setValue('')}>清空</button>
</div>
);
}
复制代码
测试步骤
- 输入
111111
,每次间隔300ms - 点击清空
- 结果 input 框内无值
结果
没问题,下一题!
但是
结果真的是这样吗?
让我们打开console在重试一下测试步骤
出现了5个1
,没有启动debounce的效果,看起来像是延迟输入了
❓❓❓
明明没有改 debounce 函数这块的内容,只是加了一个 state,就不一样了?
为什么会这样 —— 内鬼 setValue
setValue是如何捣乱的呢?
第一次输入 1
input onChange
触发,event.target.value
的值是1,setValue
触发 react 视图更新,然后开始执行 fn 函数
第二次输入 1
react 执行 App()
,debounce(() => console.log(1), 500);
返回一个新的函数(注意这里的函数不在是之前的 fn,而是一个新的fn, fn !== fn), input onChange
触发,event.target.value
的值是11,setValue
触发 react 视图更新,然后开始执行 fn 函数
…
这里其实已经可以看出端倪了。
debounce 通过执行返回一个函数,利用闭包缓存了 timer 这个变量,但是debounce函数多次执行会返回多个函数,这个时候就不在共享同一个闭包了,setValue 触发了函数的执行,每一次执行就生成了一个新的函数,和之前的 fn不在共享同一个闭包了。
改一下
思路:只要 debounce 都是返回同一个函数就可以了,我们利用useCallback
来实现
function App() {
const [value, setValue] = useState('');
const fn = useCallback(debounce(() => console.log(1), 500), []);
return (
<div>
<input
value={value}
onChange={(event) => {
const _v = event.target.value;
setValue(_v);
fn();
}}
/>
<br />
<button onClick={() => setValue('')}>清空</button>
</div>
);
}
复制代码
完成:
但是就没问题了吗?
我们试着在这个函数里面读取一下 value 的值。
function App() {
const [value, setValue] = useState('');
const fn = useCallback(
debounce(() => console.log(value), 500),
[]);
return (
<div>
<input
value={value}
onChange={(event) => {
const _v = event.target.value;
setValue(_v);
fn();
}}
/>
<br />
<button onClick={() => setValue('')}>清空</button>
</div>
);
}
复制代码
会发现,不论你怎么输入,值都是空字符,也就是value的初始值。
因为 useCallback 缓存了这个函数,这个函数依旧保留着自己的闭包。
通过useRef解决一下吧
function App() {
const [value, setValue] = useState('');
const f = () => console.log(value);
const fRef = useRef();
fRef.current = f;
const fn = useCallback(
debounce(() => fRef.current(), 500),
[]);
return (
<div>
<input
value={value}
onChange={(event) => {
const _v = event.target.value;
setValue(_v);
fn();
}}
/>
<br />
<button onClick={() => setValue('')}>清空</button>
</div>
);
}
复制代码
纸上得来终觉浅,大家可以自己尝试一下。可以加我,一起讨论解惑
自定义hooks
function useDebounce(fn, ms) {
const fRef = useRef();
fRef.current = fn;
const result = useCallback(
debounce(() => fRef.current(), ms),
[]);
return result;
}
复制代码
Voom,这里是前端骑士团乌瑟尔。
如果你需要内推,如果你需要摸鱼,加群加我++++js_xiaomayi