每天对自己多问几个为什么,总是有着想象不到的收获。 一个菜鸟小白的成长之路(copyer)
它们的本质就是对高频执行代码的一种优化手段。
节流(throttle): n 秒内只运行一次,若在 n 秒内重复触发,只有一次执行
防抖(debounce): n秒后执行该函数,如果在n秒被重复触发,则会重新计时
利用技术: 定时器 + 闭包
节流函数:
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中,向使用自定义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.throttle | Lodash 中文文档 | Lodash 中文网 (lodashjs.com)
防抖:lodash.debounce | Lodash 中文文档 | Lodash 中文网 (lodashjs.com)
以前只是了解防抖节流的基本概念,然一直没有手动亲自敲一遍,以致于可能对这个了解的不是很清楚,也可能在面试的时候,手写不出来吧。所以这次手动敲一遍,加深一下自己的印象,也可能更好的解释出清除更好的逻辑。而且这次还亲手尝试了自定义hooks来写防抖和节流,也对react hooks的渲染流程有着更进一步的认识,还是收货挺大的。
如果上面有误,大佬请指教,虚心接受~