防抖和节流的函数封装(js版本 、 react+ts+hooks版本)

  • Post author:
  • Post category:其他


每天对自己多问几个为什么,总是有着想象不到的收获。


一个菜鸟小白的成长之路(copyer)



概念解释

它们的本质就是对高频执行代码的一种优化手段。


节流(throttle)

: n 秒内只运行一次,若在 n 秒内重复触发,只有一次执行


防抖(debounce)

: n秒后执行该函数,如果在n秒被重复触发,则会重新计时

利用技术: 定时器 + 闭包



javascript版本


节流函数:

function throttle(fn, delay = 500) {
    let timer = null
    return function(...args) {
        if(!timer) {   //n秒内一直触发,timer一直为null,就不会执行判断语句中的逻辑(关键)
            timer = setTimeout(() => {
                fn.apply(this, args)
                timer = null
            }, delay)
        }
    }
}


防抖函数:

function debounce(fn, delay = 500, immediate) {
	let timer = null
	return function(...args) {
        const that = this
        if(timer) {
            clearTimeout(timer)    //直接清除定时器(关键)
            timer = null
        }
        if(immediate) {
            let rightNow = !timer 
            timer = setTimeout(() => {
                timer = null
            }, delay)
            if(rightNow) {
                fn.apply(that, args)
            }
        } else {
            timer = setTimeout(() => {
                fn.apply(that, args)
            }, delay)
        }
	}
}


基本使用:

<button class="throttle">节流</button>
<button class="debounce">防抖</button>

<script>
	function fn() {
        console.log('fn函数执行')
    }
    
    const btn = document.getElementsByClassName('throttle')[0]
    btn.addEventListener('click', throttle(fn, 2000))

    const btn1 = document.getElementsByClassName('debounce')[0]
    btn1.addEventListener('click', throttle(fn, 2000))
</script>



react + ts + hooks版本

在react中,向使用自定义hooks来一个防抖的钩子函数,一般我们就是在上面的js版本上稍微做做修改,就行了。(是对的吗? 往下看)


自定义

useDebounce

函数

export const useDebounce(fn: Function, delay = 500, immediate?: boolean) {
    return debounce(fn, delay, immediate)  //debounce是js版本的方法
}


基本使用:

import React from 'react'
import { useDebounce } from './utils/useDebounce'

const Main: React.FC = () => {
    const [test, setTest] = React.useState(12)
    const aa = () => {
        setTest((prev) => {
            return prev + 1
        })
    }
    return (
        <div>
            <h1>{test}</h1>
            <button onClick={useDebounce(aa, 1000)}>点击</button>
        </div>
    )
}

测试一下,感觉效果还行,正常的现象;(真的是对的吗?请继续往下看,稍微改下状态)。如下面:

就在useEffect中加了一个定时器,不停的改变state的状态

const Main: React.FC = () => {
    const [test, setTest] = React.useState(12)
    const [counter2, setCounter2] = React.useState(0);
    const aa = () => {
        setTest((prev) => {
            return prev + 1
        })
    }
    React.useEffect(function() {
        const t = setInterval(() => {
          setCounter2(x => x + 1)
        }, 1000);
        return clearInterval.bind(undefined, t)
    }, [])
    return (
        <div>
            <h1>{test}</h1>
            <h1>{counter2}</h1>
            <button onClick={useDebounce(aa, 1000)}>点击</button>
        </div>
    )
}

当你发现,这下我们写的防抖函数,就没有效果了,就只有延迟的效果。为什么呢?

这里就涉及到了react的hooks渲染问题,每当组件渲染一次,里面的状态就会重新进行初始化,那么我们timer就失去缓存的效果了,timer是防抖函数的核心,核心失效了,那么防抖函数也就无用了。

那么我们应该如何解决上面的问题呢?


hooks中提供了

useRef

这个hook。


useRef的最大特点: 在函数组件中定义一个全局变量,不会因为重复render重复声明,类似于组件的this.xxx。

所以,我们只需要在上面的代码上修改一下变量的定义就行了。


useDebounce()函数的实现

import React, { useRef } from "react"
interface ICurrent {
    fn: Function,
    timer: null | NodeJS.Timeout
}
export const useDebounce = (fn: Function, delay = 500, immediate?: boolean) => {
    const { current } = useRef<ICurrent>({ fn, timer: null }) //使用useRef来保存变量
	return function(...args: any[]) {
        const that = this
        if(current.timer) {
            clearTimeout(current.timer)    //直接清除定时器(关键)
            current.timer = null
        }
        if(immediate) {
            let rightNow = !current 
            current.timer = setTimeout(() => {
                current.timer = null
            }, delay)
            if(rightNow) {
                current.fn.apply(that, args)
            }
        } else {
            current.timer = setTimeout(() => {
                current.fn.apply(that, args)
            }, delay)
        }
	}
}

这样写,就可以改变上面的存在的问题了。

那么根据上面的逻辑,那我们也可以下一个自定义的

useThrottle函数


useThrottle()函数实现

export const useThrottle = (fn: Function, delay = 500) => {
    const { current } = useRef<ICurrent>({ fn, timer: null })
    return function(...args: any[]) {
        if(!current.timer) {   //n秒内一直触发,timer一直为null,就不会执行判断语句中的逻辑(关键)
            current.timer = setTimeout(() => {
                current.fn.apply(this, args)
                .current.timer = null
            }, delay)
        }
    }
} 



第三库的推荐: lodash

节流:

lodash.throttle | Lodash 中文文档 | Lodash 中文网 (lodashjs.com)

防抖:

lodash.debounce | Lodash 中文文档 | Lodash 中文网 (lodashjs.com)



总结

​ 以前只是了解防抖节流的基本概念,然一直没有手动亲自敲一遍,以致于可能对这个了解的不是很清楚,也可能在面试的时候,手写不出来吧。所以这次手动敲一遍,加深一下自己的印象,也可能更好的解释出清除更好的逻辑。而且这次还亲手尝试了自定义hooks来写防抖和节流,也对react hooks的渲染流程有着更进一步的认识,还是收货挺大的。

​ 如果上面有误,大佬请指教,虚心接受~



版权声明:本文为James_xyf原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。