总结防抖节流,在react中使用

548 阅读3分钟

js防抖节流

使用场景

  • 防抖:事件触发后延迟n秒在执行,如果在这n秒内再次触发则重新计时。即在一段时间内只允许事件执行一次,常用于表单提交,输入框防抖
  • 节流:事件触发后延迟n秒在执行,并且在这n秒内再次触发事件时不允许执行。即减少一段时间内事件触发的频率,常用于监听滚动条滚动,鼠标移动,窗口大小变化
  • 借鉴掘友的神评:防抖类似技能冷却,点击后会有冷却时间;节流类似网络延迟,点击后多少ms后触发

初始延迟执行

    // 防抖
    function debounce(fn, delay) {
        let timer = null;
        clearTimeout(timer); // 下次调用时会清除上次的timer, 然后重新延迟
        timer = setTimeout(function(){
            fn();
        }, delay);
    } 
  // 节流
  const throttle = (fn, delay) => {
    let flag = true
    return () => {
      if (!flag) return
      flag = false // ms 内不允许再次执行
      setTimeout(() => {
        fn()
        flag = true // 重置为true,允许执行
      }, delay)
    }
  }

初始立即执行

    // 防抖
    function debounce(fn, wait) {
      let timer = null
      return function () {
          let args = arguments
          if(timer == null){
            fn.apply(this, args)
          }
          timer && clearTimeout(timer)
          timer = setTimeout(() => {
            fn.apply(this, args)
          }, wait)
      }
    }
  // 节流
  function throttle(fun,time){
    let t1=0 //初始时间
    return function(){
      let t2=new Date() //当前时间
      if(t2-t1>time){
        fun.apply(this,arguments)
        t1=t2
      }
    }
  }

react函数组件使用防抖、节流

在实践过程中,函数组件不能使用js方法,因为定时器timer每次都会重新定义,timer是绑定了自己的id,重新定义后每次都为undefined,定时器就失去效果

通过useRef实现,能再函数组件中使用(可能会有bug)

    import React, { useRef } from 'react';
    import { Button } from 'antd';
    const HomePage = () => {
      const timeOutRef = useRef(null)
      // 防抖
      function debounce(fn, delay) {
        clearTimeout(timeOutRef.current); // 下次调用时会清除上次的timer, 然后重新延迟
        timeOutRef.current = setTimeout(function(){
            fn();
        }, delay);
    }
      // 节流
      function throttle(fn, delay) {
        if (!timeOutRef.current) {
          timeOutRef.current = setTimeout(() => {
            timeOutRef.current = null
                fn()
            }, delay)
        }
      }
      function textHandle(args) {
        console.log('测试');
      }
      const handleClick = () => {
        debounce(textHandle, 2000)
      }
      return (
        <div>
          <Button onClick={handleClick}>主页面</Button>
        </div>
      );
    };

    export default HomePage;

通过自定义hook实现

防抖useDebounce
    import { useRef, useCallback } from 'react'
    type FnType = (...arg: any[]) => any
    interface RefType {
      fn: FnType
      timer: NodeJS.Timeout | null
    }
    function useDebounce(this: any, fn: FnType, delay: number, dep: any[] = []) {
      //  使用 useRef 的目的是:保留上一次的timer,以至于让 if 语句走通,然后清除上一次的 timer
      // 否则,没有清除定时器,达不到防抖效果
      const { current } = useRef<RefType>({ fn, timer: null })
      current.fn = fn
      return useCallback((...args) => {
        if (current.timer) {
          clearTimeout(current.timer)
        }
        
        // 下面这个判断,是初始是否执行
        if(current.timer == null) {
          current.fn.apply(this, args)
        }
        current.timer = setTimeout(() => {
          current.fn.apply(this, args)
        }, delay)
      }, dep)
    }

    export default useDebounce
使用
    import React from 'react';
    import { Button } from 'antd';
    import useDebounce from './useDebounce.ts';
    const HomePage = () => {
      function textHandle(args) {
        console.log(args,'测试');
      }
      const handleClick = useDebounce(textHandle, 2000);
      return (
        <div>
          <Button onClick={handleClick}>测试</Button>
        </div>
      );
    };

    export default HomePage;

节流useThrottle
    import { useRef, useCallback } from 'react'
    type FnType = (...arg: any[]) => any
    interface RefType {
      fn: FnType
      timer: NodeJS.Timeout | null
    }
    function useThrottle(this: any, fn: FnType, delay: number, dep: any[] = []) {
      const { current } = useRef<RefType>({ fn, timer: null })
      current.fn = fn
      // console.log('this', this)
      return useCallback((...args) => {
        if (!current.timer) {
          current.timer = setTimeout(() => {
            current.timer = null
          }, delay)
          current.fn.apply(this, args)
        }
      }, dep)
    }

    export default useThrottle
使用
    import React from 'react';
    import { Button } from 'antd';
    import useThrottle from './useThrottle.ts';
    const HomePage = () => {
      function textHandle(args) {
        console.log(args,'测试');
      }
      const handleClick = useThrottle(textHandle, 2000);
      return (
        <div>
          <Button onClick={handleClick}>测试</Button>
        </div>
      );
    };

    export default HomePage;

总结

  • js方法在react中不适用,并且都可以判断初始是否调用
  • react方法中需要使用useRef保存计时器,简单版本初始都不会调用,自定义hook初始都会调用

参考链接