防抖节流以及React Hook中的防抖节流

时间:2025-01-20 19:24:08

防抖节流以及React Hook中的防抖节流

  • 一、什么是节流防抖?
  • 二、防抖节流实现方式
    • 1.防抖实现方式
    • 2.节流实现方式
  • 三、在React Hooks中使用防抖节流
    • React Hooks 防抖
    • React Hooks 节流
  • 四、Umi Hooks官方防抖节流


一、什么是节流防抖?

  • 防抖:触发事件后在n秒内函数只能执行一次,倘若在n秒内再次触发事件,则会重新计算函数执行时间。比如input输入框的change事件,在600ms内触发一次,如果在300ms时input框的内容再次发生变化,那么change事件将在600ms后触发。
  • 节流:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发送一次请求。

二、防抖节流实现方式

1.防抖实现方式

代码如下:

/**
 * desc 防抖函数
 * @param {*} func  函数
 * @param {*} wait  延迟毫秒数
 * @returns 
 */
function debounce(func,wait){ 
    let timer;
    return function(){
        const _this = this; // 解决 setTimeout中this的指向问题
        let arg = arguments; // 解决 func中参数丢失的问题
        if(timer) clearTimeout(timer); // 每次执行的时候把前一个 setTimeout clear 掉
        timer = setTimeout(() => {
            func.apply(_this,arg) 
        },wait)
    }
}

此时函数不能立即执行,增加immediate参数,如下:

/**
 * desc 防抖函数
 * @param {*} func 函数
 * @param {*} wait 延迟毫秒数
 * @param {*} immediate  true 立即执行, false 非立即执行
 * @returns 
 */
function debounce(func,wait,immediate){ // 函数不能立即执行,增加参数immediate
	let timer = null;
    return function(){
        const _this = this; // 解决 setTimeout中this的指向问题
        let arg = arguments; // 解决 func中参数丢失的问题
        if(timer) clearTimeout(timer);
        if(immediate){ // 使函数立即执行
            let runNow = !timer;
            timer = setTimeout(function () {
                timer = null;
            },wait)
            if(runNow){
                func.apply(_this,arg) 
            }
        }else{
            timer = setTimeout(() => {
                func.apply(_this,arg) 
            },wait)
        }
    }
}

2.节流实现方式

代码如下:

/**
 * desc 节流函数
 * @param {*} func 函数
 * @param {*} wait 毫秒数
 * @returns 
 */
function throttle(func, wait) {
    let timer;
    return function () {
        let _this = this;
        if (!timer) {
            timer = setTimeout(() => {
                timer = null;
                func.apply(_this, arguments)
            }, wait)
        }
    }
}

三、在React Hooks中使用防抖节流

首先让我们看看在react类组件中使用防抖:

import debounce from 'lodash/debounce';  // 这里使用lodash

export default class Search extends Component {
    constructor(props) {
      super(props)
      this.handleSearch = debounce(this.handleSearch, 500);
    }
    handleSubmit = (e) => {
        e.preventDefault();
        this.handleSearch();
    }
    handleSearch = () => {
    ...
    }
    render() {
        return (
            <form onSubmit={this.handleSubmit}></form>
        )
    }
}

当我们在函数式组件使用防抖函数时,自然而然想到这么做:

import React, {useState} from 'react';
import debounce from 'lodash/debounce';
const sendQuery = (query) => console.log(`Querying for ${query}`);
const Search = () => {
  const [userQuery, setUserQuery] = useState("");
  const delayedQuery = debounce(q => sendQuery(q), 500);
  const onChange = e => {
    setUserQuery(e.target.value);
    delayedQuery(e.target.value);
  };
  return (
    <div>
      <label>Search:</label>
      <input onChange={onChange} value={userQuery} />
    </div>
  );
}

然而,这样做防抖函数并没有发挥作用,仅仅是把函数时间延后了500毫秒。这是因为函数组件每次渲染结束之后,内部的变量都会被释放,重新渲染时所有的变量会被重新初始化,产生的结果就是每一次都注册和执行了setTimeout函数。想要得到正确的运行结果,必须以某种方式存储那些本会被删除的变量和方法的引用。很遗憾,没办法直接使用useState去存储,但是我们可以利用React组件的缓存机制,通过自定义Hook组件去解决这个问题。

React Hooks 防抖

代码如下:

import { useEffect, useCallback, useRef } from 'react';

// 防抖 hooks
function useDebounce(fn, delay, dep = []) {
    const { current } = useRef({ fn, timer: null });
    useEffect(function () {
        current.fn = fn;
    }, [fn]);

    return useCallback(function f(...args) {
        if (current.timer) {
            clearTimeout(current.timer);
        }
        current.timer = setTimeout(() => {
            current.fn.call(this, ...args);
        }, delay);
    }, dep)
}

export default useDebounce;

React Hooks 防抖使用示例,代码如下:

import React, {useState} from 'react';
import useDebounce from '@/components/UseDebounce';
const sendQuery = (query) => console.log(`Querying for ${query}`);
const Search = () => {
  const [userQuery, setUserQuery] = useState("");
  const delayedQuery = useDebounce(q => sendQuery(q), 500);
  const onChange = e => {
    setUserQuery(e.target.value);
    delayedQuery(e.target.value);
  };
  return (
    <div>
      <label>Search:</label>
      <input onChange={onChange} value={userQuery} />
    </div>
  );
}

React Hooks 节流

代码如下:

import { useEffect, useCallback, useRef } from 'react';

function useThrottle(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null })
  useEffect(function () {
     current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (!current.timer) {
      current.timer = setTimeout(() => {
        delete current.timer
      }, delay)
      current.fn.call(this, ...args)
    }
  }, dep)
}

export default useThrottle;

四、Umi Hooks官方防抖节流

Umi 官方提供有hooks的防抖节流方法,可直接使用。

官方示例 (useDebounce 处理值防抖):

import { Input } from 'antd';
import React, { useState } from 'react';
import { useDebounce } from '@umijs/hooks';
export default () => {
  const [value, setValue] = useState();
  const debouncedValue = useDebounce(value, 500);
  return (
    <div>
      <Input
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>DebouncedValue: {debouncedValue}</p>
    </div>
  );
};

官方示例(useDebounceFn 处理函数防抖):

import React, { useState } from 'react';
import { Button } from 'antd';
import { useDebounceFn } from '@umijs/hooks';

export default () => {
  const [value, setValue] = useState(0);
  const { run } = useDebounceFn(() => {
    setValue(value + 1);
  }, 500);

  return (
    <div>
      <p style={{ marginTop: 16 }}> Clicked count: {value} </p>
      <Button onClick={run}>Click fast!</Button>
    </div>
  );
};

官方示例 (useThrottle 处理值节流):

import { Input } from 'antd';
import React, { useState } from 'react';
import { useThrottle } from '@umijs/hooks';
export default () => {
  const [value, setValue] = useState();
  const throttledValue = useThrottle(value, 500);
  return (
    <div>
      <Input
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>throttledValue: {throttledValue}</p>
    </div>
  );
};

官方示例(useThrottleFn 处理函数节流):

import React, { useState } from 'react';
import { Button } from 'antd';
import { useThrottleFn } from '@umijs/hooks';
export default () => {
  const [value, setValue] = useState(0);
  const { run } = useThrottleFn(() => {
    setValue(value + 1);
  }, 500);
  return (
    <div>
      <p style={{ marginTop: 16 }}> Clicked count: {value} </p>
      <Button onClick={run}>Click fast!</Button>
    </div>
  );
};

Umi Hooks官网 /zh-CN