刷掘金时无意看到了一篇文章:React hooks 怎样做防抖?
点进去看了一遍么,才发现之前对于hooks仅仅只是会用,对于hook的渲染机制并不十分的了解。
我们先从上篇博客提到防抖来入手,再谈渲染机制。
我们平时是如何写防抖的?
function debounce(fn,delay) {
let timer;
return function (...arg) {
if (timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn(arg)
clearTimeout(timer)
},delay)
}
}
这是一个很普通的防抖函数,他的原理很简单,利用闭包做一个缓存的变量即可实现
防抖:如果在规定时间内做点击,在设定时间内若上一次还未执行,则重新及时,到达时间后执行(可以理解为法师施法,打断了就要重新施法)
那我们在hooks中是如何使用的呢?
你可能会说,我们直接上用上面那个不就ok了
function useDebounce(fn,delay){
return debounce(fn,delay) //我直接复用上面定义的防抖代码
}
ok那我们做一个示例:
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const handleClick = useDebounce(function() {
//console.count('click1')
setCounter1(counter1 + 1)
}, 500)
function debounce(fn,delay) {
let timer;
return function (...arg) {
if (timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn(arg)
clearTimeout(timer)
},delay)
}
}
function useDebounce(fn,delay){
return debounce(fn,delay) //我直接复用上面定义的防抖代码
}
// 引入间隔计时对count2++
useEffect(function() {
console.log("useEffect被调用")
const t = setInterval(() => {
setCounter2(x => x + 1)
}, 500);
//return的函数 会在调用一个新的 effect 之前对前一个 effect 进行清理
return clearInterval.bind(undefined, t) //这个清除阶段 每次都会在下次useEffect执行前执行
}, [])
return <div style={{ padding: 30 }}>
<button
ref={inputEl}
onClick={function() {
handleClick()
}}
>click</button>
<div>{counter1}</div>
<div>{counter2}</div> //counter2在这里
</div>
这里引入了一个setInterval,他不停的对counter2++,目的是为了引起这个组件渲染,接下来我们看看防抖效果
上图是在hooks调用的,防抖函数的延时时间传入1000ms
我在1000ms内进行了多次点击,理论上他应该再等1000ms,但他却在我第一次点击1s触发了。
即防抖失效
接下来我们来想想为什么?
hooks的一个渲染机制
我们平时在class组件,调用这个防抖函数他就可以渲染,即防抖内的闭包生效,但是到了hooks中却失效了,这是因为hooks在执行渲染会将所有的hooks重新去执行一遍,这样我们之前定义的timer他就不存在被重新渲染了,可以结合Hook渲染每次渲染顺序必须一致来理解。
你可能问为什么我在class组件就能用呢?
仔细想想我们就可以想到,class组件的重新渲染是render下的JSX那块的代码,在上面定义的方法并未重新的去渲染
因此我们可以明白,这是一个渲染执行而导致的问题,接下来我们用hooks的缓存机制来解决上面的防抖。
//对之前的Debounce进行改造
function useDebounce(fn,delay) {
const {current} = useRef({}); //ref改变时不会改变任何值
//且 其 .current 属性中保存一个可变值的“盒子”,其中可以保存任何值 我们就可以利用这样一个缓存机制
// 当然他也有其他缓存机制,例如useCallBack和useMemo,但是我实在想不通,如何把timer提出去,除了使用useRef
/*
*
* 原因:因为在class组件,每次重新渲染的都是render下的jsx模板,
* hook不一样,他每次都会重新渲染全部的hook,因此会导致闭包每次的失效,
* 不然为什么官方文档中会去做规定,每次渲染的位置必须都是一致的
* */
function f(...args){
if (current.timer) {
clearTimeout(current.timer);
}
current.timer = setTimeout(fn.bind(undefined, ...args), delay);
}
return f
}
const handleClick = useDebounce(function() {
setCounter1(counter1 + 1)
}, 500)
我们借useRef来实习一个缓存机制,关于useRef移步官方文档
PS:这其中只是包含我个人的一个理解,如果有不太对的地方还请指正交流啦ovo
参考文章:https://juejin.im/post/5ea04691e51d4547126400c7