如何在React hooks里面正确的使用debounce函数

通过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>
    );
}
复制代码

测试步骤

  1. 输入111111,每次间隔300ms
  2. 点击清空
  3. 结果 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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享