- 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>
- );

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就会跟着渲染一次。(造成了不必要的渲染,复杂业务逻辑下下,会对项目有影响)

-
-
- 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变量。也验证了闭包的含义:
我的源代码: https://github.com/BBbila/my-mixed-bag/tree/main/src/ReatHooks/throttleJitter