如何使用钩子去抖功能组件中的回调

How to debounce a callback in functional component using hooks

我怎样才能在 React 功能组件去抖动回调中获得实际的 prop 值,它在 React Class 组件中工作,但我不知道如何使用钩子在功能组件中实现这种行为。

import React from "react";
import ReactDOM from "react-dom";
import debounce from "lodash.debounce";

const TestFunc = ({ count, onClick }) => {
  const handleClick = debounce(() => {
    onClick();
    console.log(count);
  }, 500);

  return (
    <div>
      <button type="button" onClick={handleClick}>
        Func: {count}
      </button>
    </div>
  );
};

class TestClass extends React.Component {
  handleClick = debounce(() => {
    this.props.onClick();
    console.log(this.props.count);
  }, 500);

  render() {
    return (
      <div>
        <button type="button" onClick={this.handleClick}>
          Class: {this.props.count}
        </button>
      </div>
    );
  }
}

const App = () => {
  const [countClass, setCountClass] = React.useState(0);
  const [countFunc, setCountFunc] = React.useState(0);

  return (
    <div>
      <TestFunc count={countFunc} onClick={() => setCountFunc(countFunc + 1)} />
      <TestClass
        count={countClass}
        onClick={() => setCountClass(countClass + 1)}
      />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

当你点击功能组件按钮时,它会将之前的 count 属性值记录到控制台,但它已经通过调用 onClick 处理程序进行了更改,同时 class组件按钮将在 onClick 处理程序递增后记录实际的 count 道具值。那么,我怎样才能在功能组件中获得实际的 prop 值呢?

您需要进行一些更改才能使用 debounced method 和 hook

  1. 您需要使用 useCallback 挂钩,以便在初始渲染时只创建一次去抖功能。
  2. 现在,如果您必须确保 debounced 在执行时获得正确的计数值,您需要将其作为参数传递,否则它将在创建时使用其封闭闭包中的值,即初始计数值。
  3. 您需要使用父组件中的回调模式更新 onClick 方法调用的计数值,例如 setCountFunc(count => count + 1),以便子组件 re-render 具有更新后的值

下面的工作演示

const TestFunc = ({ count, onClick }) => {
  const handleClick = React.useCallback((count) =>{
     const click = _.debounce((count) => {
          onClick();
          console.log(count);
     }, 500)
     click(count);
 }, []);

  console.log(count, 'render');
  return (
    <div>
      <button type="button" onClick={() => handleClick(count)}>
        Func: {count}
      </button>
    </div>
  );
};

class TestClass extends React.Component {
  handleClick = _.debounce(() => {
    this.props.onClick();
    console.log(this.props.count);
  }, 500);

  render() {
    return (
      <div>
        <button type="button" onClick={this.handleClick}>
          Class: {this.props.count}
        </button>
      </div>
    );
  }
}

const App = () => {
  const [countClass, setCountClass] = React.useState(0);
  const [countFunc, setCountFunc] = React.useState(0);

  return (
    <div>
      <TestFunc count={countFunc} onClick={() => setCountFunc(count => count + 1)} />
      <TestClass
        count={countClass}
        onClick={() => setCountClass(countClass + 1)}
      />
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root" />

这是一个简单的去抖挂钩(用 TypeScript 编写)

import { useEffect, useRef } from "react";

export function useDebouncedCallback<A extends any[]>(
  callback: (...args: A) => void,
  wait: number
) {
  // track args & timeout handle between calls
  const argsRef = useRef<A>();
  const timeout = useRef<ReturnType<typeof setTimeout>>();

  function cleanup() {
    if(timeout.current) {
      clearTimeout(timeout.current);
    }
  }

  // make sure our timeout gets cleared if
  // our consuming component gets unmounted
  useEffect(() => cleanup, []);

  return function debouncedCallback(
    ...args: A
  ) {
    // capture latest args
    argsRef.current = args;

    // clear debounce timer
    cleanup();

    // start waiting again
    timeout.current = setTimeout(() => {
      if(argsRef.current) {
        callback(...argsRef.current);
      }
    }, wait);
  };
}

您的用例示例:

const handleClick = useDebouncedCallback(() => {
  onClick();
  console.log(count);
}, 500);

... 

<button type="button" onClick={handleClick}>
  Func: {count}
</button>

也适用于传递参数的情况:

const handleChange = useDebouncedCallback((event) => {
  console.log(event.currentTarget.value);
}, 500);

<input onChange={handleChange}/>

去抖API调用功能组件

<input
 type="text"
 placeholder="Search"
 onChange={(e) => search(e.target.value)}
/>
=========================================================================
  const [typingTimeout, setTypingTimeout] = useState(0);
const search = async (value) => {
    if (typingTimeout) {
      clearTimeout(typingTimeout);
    }
    setTypingTimeout( setTimeout(() => {
        goToSearch(value);
      }, 1000)
    );

  }
  const goToSearch = async (value) => {
    const response = await getData(args);
  }

class 个组件 与上述相同,但搜索功能发生变化

const search = (event.target.value) =>{
    if (this.state.typingTimeout) {
      clearTimeout(this.state.typingTimeout);
    }
    this.setState({
      typingTimeout: setTimeout(()=> {
        this.goToSearch(event.target.value);
      }, 1000)
    });
}

打字稿

setTypingTimeout( window.setTimeout(() => {
          goToSearch(value);
        }, 1000)
      );

使用window.setTimeout