-
目录
useThrottle: 封装了一个节流的hook
import { useEffect, useCallback, useRef } from 'react';
function useThrottle(fn: Function, delay: any, dep = []) {
//用useRef --确保 useCallback里面的值是最新的,如果用useState会形成闭包,导致return不了最新的函数
const { current } = useRef<any>({ fn, timer:null })
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(this: any) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer
}, delay)
current.fn.call(this, arguments)
}
}, dep)
}
export default useThrottle;
使用:
/** 点击事件 */
function handleClickJitter(type = '') {
if(type == 'debounce') {
setText("已处理")
}else {
console.log("执行一次");
setThrottleText(`${new Date().getSeconds()}`)
}
}
return (
<Fragment>
<button onClick={useThrottle(() =>{handleClickJitter('throttle')},3000)} />
<div className={textThrottleCls}>{throttleText}</div>
</div>
</Fragment>
);
-
useCallback的作用(性能优化)
useCalback就是对函数起一个“缓存”作用,当该函数被父组件作为props传递给字组件子组件时,让props“无变化”,因此达到不会让子组件重复渲染的目的。
函数式子组件需要搭配 React.memo使用!!!
无论子组件是pureComponent还是用React.memo进行包裹,都会让子组件render,而配合useCallback使用就能让子组件不随父组件render。
const BtnMemo = React.memo((props: any) => {
const { click } = props
console.log("渲染一次");
return (
<button className="down" onClick={click}>节流</button>
)
})
const throttleJitterCon = (props: IThrottleJitterConProps) => {
const [throttleText, setThrottleText] = useState("初始")
/** 点击事件 */
function handleClickJitter(type = '') {
if(type == 'debounce') {
setText("已处理")
}else {
console.log("执行一次");
setThrottleText(`${new Date().getSeconds()}`)
}
}
return (
<Fragment>
<button click={useThrottle(() =>{handleClickJitter('throttle')},3000)} />
<div className={textThrottleCls}>{throttleText}</div>
</div>
</Fragment>
);
看反例,去掉useCallback的情况:
function useThrottle(fn: Function, delay: any, dep = []) {
//用useRef --确保 useCallback里面的值是最新的,如果用useState会形成闭包,导致return不了最新的函数
const { current } = useRef<any>({ fn, timer:null })
useEffect(function () {
current.fn = fn;
}, [fn]);
return function f(this: any) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer
}, delay)
current.fn.call(this, arguments)
}
}
}
可以看到,执行一次后,btnMemo就会跟着渲染一次。(造成了不必要的渲染,复杂业务逻辑下下,会对项目有影响)
不用Hook封装节流方法的情况,看是怎么形成闭包的:
const BtnMemo = React.memo((props: any) => {
const { click } = props
return (
<button className="down" onClick={click}>节流</button>
)
})
const throttleJitterCon = (props: IThrottleJitterConProps) => {
const [throttleText, setThrottleText] = useState("初始")
/** 节流:在一定时间内只能执行一次 */
const throttleFunc = (fn: Function, delay:any) => {
let timer: any
return useCallback(function(this:any) {
console.log("throttleText--useCallback",throttleText);
if (!timer) {
timer = setTimeout(() => {
timer = null
}, delay)
fn.apply(this, arguments)
}
},[])
}
console.log("throttleText++++++++++++",throttleText);
/** 点击事件 */
function handleClickJitter(type = '') {
if(type == 'debounce') {
setText("已处理")
}else {
setThrottleText(`${new Date().getSeconds()}`)
}
}
return (
<Fragment>
<div className="btnGroup">
<BtnMemo click={throttleFunc(() =>{handleClickJitter('throttle')},3000)} />
<div className={textThrottleCls}>{throttleText}</div>
</div>
</Fragment>
);
}
export default throttleJitterCon;
throttleFunc函数里是可以访问外部作用域的 throttleText变量。也验证了闭包的含义:
- 红宝书:闭包是指有权访问另一个函数作用域中变量的函数
- MDN: 闭包是指那些能够访问自由变量的函数,这里的自由变量是指外部作用域中的变量
我的源代码: https://github.com/BBbila/my-mixed-bag/tree/main/src/ReatHooks/throttleJitter