抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

防抖是指触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间。

如果让一些事件响应函数的响应频率无限制,不仅会加重浏览器的负担,而且容易导致页面卡顿等影响用户的体验。所以使用防抖可以来限制事件处理函数的调用频率,提升用户的体验,同时又不影响实际的效果。

简单的防抖函数

利用 Javascript 闭包的特性,保存一个计时器 Timeout,在其子函数中对其进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
function debounce(fn, wait) {
var timeout
return function () {
var context = this,
args = arguments
clearTimeout(timeout)

timeout = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}

函数的运行过程如下:

  1. 触发函数后创建计时器
  2. 在计时器结束之前若再次触发函数则更新重置计时器
  3. 计时器结束时触发函数 fn

React 函数式组件中的防抖函数

上面的 debounce 函数在 React 中无法生效,原因是函数式组件每次渲染,函数都会被重建,导致 debounce 函数中的 timer 会重新创建,进而导致防抖失效。目前网络上提供了两种比较常用的方法:

  1. 使用 useRef 来缓存计时器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function useDebounce(fn,delay){
    //fn为需要防抖的函数
    const refTimer = useRef();

    return function f(...args){
    if(refTimer.current){
    clearTimeout(refTimer.current);
    }
    refTimmer.current = setTimeout(() => {
    fn(args);
    },delay)
    }
    }
  2. 使用 useCallback 缓存函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Debounce(){
    const click = useCallback(clickInner(),[]);

    function clickInner(){
    let timer;
    return function (e){
    if(timer){
    clearTimeout(timer);
    }
    timer = setTimeout(() => {
    //todo
    //事件 e 的相关操作
    },1000)
    }
    }
    }

以上两种方法时防抖函数在 React 组件式函数中的应用。

React + Hook + Typescript 中的防抖函数

Typescript 中增加了静态类型定义,对上面的函数稍加修改即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useCallback, useEffect, useRef } from "react";

interface ICurrent {
fun: Function,
timer: null | NodeJS.Timeout
}

function useDebounce(fn: (args: any) => void, delay: number, dep:any = []) {
const { current } = useRef<ICurrent>({ fun: fn, timer: null });
useEffect(function () {
current.fun = fn;
}, [fn]);

return useCallback((args: any) => {
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(() => {
current.fun(args);
}, delay)
}, dep)
}

export default useDebounce;

调用方式如下(以 Ant Design 中的 Slider 组件为例):

1
2
3
4
5
6
7
8
9
10
11
12
const onChangeR = useDebounce((newValue: number) => {
fireColor.R = newValue;
setColorState(fireColor);
setInputValueR(newValue);
changeColor();
}, 500);
return (<Slider
min={0}
max={255}
onChange={onChangeR}
value={typeof inputValueR === 'number' ? inputValueR : 0}
/>)

实际上自己写的防抖函数在 Ant Design 的 Slider 组件上的表现并不好,其 Slider 组件本身就提供了 onChange 和 onAfterChange 两个不同的 API,其中 onAfterChange 与 onMouseUp 触发时机一致,传入值为 Slider 的当前值,完全可以将 onAfterChange 当作是防抖后的 onChange 使用。

在编写和使用自定义的 useDebounce 的时候,还遇到了一些其他的困难,它们与 React Hook 的特性相关,我将在之后的 React 学习笔记中进行介绍和分析。

评论