如何在React.js中执行debounce ?

我想撤消handleOnChange。

我尝试debounce(这。handleOnChange, 200)但它不起作用。

function debounce(fn, delay) {
  var timer = null;
  return function() {
    var context = this,
      args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

var SearchBox = React.createClass({
  render: function() {
    return <input type="search" name="p" onChange={this.handleOnChange} />;
  },

  handleOnChange: function(event) {
    // make ajax call
  }
});

当前回答

对于debounce,你需要在event.persist()中保留原始的合成事件。下面是用React 16+测试的工作示例。

import React, { Component } from 'react';
import debounce from 'lodash/debounce'

class ItemType extends Component {

  evntHandler = debounce((e) => {
    console.log(e)
  }, 500);

  render() {
    return (
      <div className="form-field-wrap"
      onClick={e => {
        e.persist()
        this.evntHandler(e)
      }}>
        ...
      </div>
    );
  }
}
export default ItemType;

使用功能组件,您可以做到这一点-

const Search = ({ getBooks, query }) => {

  const handleOnSubmit = (e) => {
    e.preventDefault();
  }
  const debouncedGetBooks = debounce(query => {
    getBooks(query);
  }, 700);

  const onInputChange = e => {
    debouncedGetBooks(e.target.value)
  }

  return (
    <div className="search-books">
      <Form className="search-books--form" onSubmit={handleOnSubmit}>
        <Form.Group controlId="formBasicEmail">
          <Form.Control type="text" onChange={onInputChange} placeholder="Harry Potter" />
          <Form.Text className="text-muted">
            Search the world's most comprehensive index of full-text books.
          </Form.Text>
        </Form.Group>
        <Button variant="primary" type="submit">
          Search
        </Button>
      </Form>
    </div>
  )
}

引用- - - - - - ——https://gist.github.com/elijahmanor/08fc6c8468c994c844213e4a4344a709 ——https://blog.revathskumar.com/2016/02/reactjs-using-debounce-in-react-components.html

其他回答

至于2021年6月,您可以简单地实现xnimorz解决方案:https://github.com/xnimorz/use-debounce

import { useState, useEffect, useRef } from "react";
// Usage
function App() {
  // State and setters for ...
  // Search term
  const [searchTerm, setSearchTerm] = useState("");
  // API search results
  const [results, setResults] = useState([]);
  // Searching status (whether there is pending API request)
  const [isSearching, setIsSearching] = useState(false);
  // Debounce search term so that it only gives us latest value ...
  // ... if searchTerm has not been updated within last 500ms.
  // The goal is to only have the API call fire when user stops typing ...
  // ... so that we aren't hitting our API rapidly.
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  // Effect for API call
  useEffect(
    () => {
      if (debouncedSearchTerm) {
        setIsSearching(true);
        searchCharacters(debouncedSearchTerm).then((results) => {
          setIsSearching(false);
          setResults(results);
        });
      } else {
        setResults([]);
        setIsSearching(false);
      }
    },
    [debouncedSearchTerm] // Only call effect if debounced search term changes
  );
  return (
    <div>
      <input
        placeholder="Search Marvel Comics"
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {isSearching && <div>Searching ...</div>}
      {results.map((result) => (
        <div key={result.id}>
          <h4>{result.title}</h4>
          <img
            src={`${result.thumbnail.path}/portrait_incredible.${result.thumbnail.extension}`}
          />
        </div>
      ))}
    </div>
  );
}
// API search function
function searchCharacters(search) {
  const apiKey = "f9dfb1e8d466d36c27850bedd2047687";
  return fetch(
    `https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`,
    {
      method: "GET",
    }
  )
    .then((r) => r.json())
    .then((r) => r.data.results)
    .catch((error) => {
      console.error(error);
      return [];
    });
}
// Hook
function useDebounce(value, delay) {
  // State and setters for debounced value
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(
    () => {
      // Update debounced value after delay
      const handler = setTimeout(() => {
        setDebouncedValue(value);
      }, delay);
      // Cancel the timeout if value changes (also on delay change or unmount)
      // This is how we prevent debounced value from updating if value is changed ...
      // .. within the delay period. Timeout gets cleared and restarted.
      return () => {
        clearTimeout(handler);
      };
    },
    [value, delay] // Only re-call effect if value or delay changes
  );
  return debouncedValue;
}
/**
 * Returns a function with the same signature of input `callback` (but without an output) that if called, smartly
 * executes the `callback` in a debounced way.<br>
 * There is no `delay` (to execute the `callback`) in the self-delayed tries (try = calling debounced callback). It
 * will defer **only** subsequent tries (that are earlier than a minimum timeout (`delay` ms) after the latest
 * execution). It also **cancels stale tries** (that have been obsoleted because of creation of newer tries during the
 * same timeout).<br>
 * The timeout won't be expanded! So **the subsequent execution won't be deferred more than `delay`**, at all.
 * @param {Function} callback
 * @param {number} [delay=167] Defaults to `167` that is equal to "10 frames at 60 Hz" (`10 * (1000 / 60) ~= 167 ms`)
 * @return {Function}
 */
export function smartDebounce (callback, delay = 167) {
  let minNextExecTime = 0
  let timeoutId

  function debounced (...args) {
    const now = new Date().getTime()
    if (now > minNextExecTime) { // execute immediately
      minNextExecTime = now + delay // there would be at least `delay` ms between ...
      callback.apply(this, args) // ... two consecutive executions
      return
    }
    // schedule the execution:
    clearTimeout(timeoutId) // unset possible previous scheduling
    timeoutId = setTimeout( // set new scheduling
      () => {
        minNextExecTime = now + delay // there would be at least `delay` ms between ...
        callback.apply(this, args) // ... two consecutive executions
      },
      minNextExecTime - now, // 0 <= timeout <= `delay` ... (`minNextExecTime` <= `now` + `delay`)
    )
  }

  debounced.clear = clearTimeout.bind(null, timeoutId)

  return debounced
}
/**
 * Like React's `useCallback`, but will {@link smartDebounce smartly debounce} future executions.
 * @param {Function} callback
 * @param {[]} deps
 * @param {number} [delay=167] - Defaults to `167` that is equal to "10 frames at 60 Hz" (`10 * (1000 / 60) ~= 167 ms`)
 */
export const useDebounced = (callback, deps, delay = 167) =>
  useMemo(() => smartDebounce(callback, delay), [...deps, delay])

我的解决方案是基于钩子(用Typescript编写)。

我有2个主要钩子useDebouncedValue和useDebouncedCallback

First - useDebouncedValue

假设我们有一个搜索框,但是我们想在用户停止输入0.5秒后向服务器请求搜索结果

function SearchInput() {
  const [realTimeValue, setRealTimeValue] = useState('');

  const debouncedValue = useDebouncedValue(realTimeValue, 500); // this value will pick real time value, but will change it's result only when it's seattled for 500ms

  useEffect(() => {
    // this effect will be called on seattled values
    api.fetchSearchResults(debouncedValue);
  }, [debouncedValue])

  return <input onChange={event => setRealTimeValue(event.target.value)} />
}

实现

import { useState, useEffect } from "react";

export function useDebouncedValue<T>(input: T, time = 500) {
  const [debouncedValue, setDebouncedValue] = useState(input);

  // every time input value has changed - set interval before it's actually commited
  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebouncedValue(input);
    }, time);

    return () => {
      clearTimeout(timeout);
    };
  }, [input, time]);

  return debouncedValue;
}

第二个useDebouncedCallback

它只是在你的组件范围内创建一个“debpublished”函数。

假设我们有一个带有按钮的组件,在你停止点击它后,它将显示警报500毫秒。

function AlertButton() {
  function showAlert() {
    alert('Clicking has seattled');
  }

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

  return <button onClick={debouncedShowAlert}>Click</button>
}

实现(注意我使用lodash/debounce作为辅助)

import debounce from 'lodash/debounce';
import { useMemo } from 'react';

export function useDebouncedCallback<T extends (...args: any) => any>(callback: T, wait?: number) {
  const debouncedCallback = useMemo(() => debounce(callback, wait), [callback, wait]);

  return debouncedCallback;
}

与其在debounce()中包装handleOnChange,不如在debounce()中包装回调函数中的ajax调用,从而不破坏事件对象。就像这样:

handleOnChange: function (event) {
   debounce(
     $.ajax({})
  , 250);
}

您可以使用引用变量来存储计时器,然后将其清除。下面是一个不使用任何第三方包在react中实现deboundation的例子

import { useState, useRef } from "react";
import "./styles.css";

export default function App() {
  // Variables for debouncing
  const [text, setText] = useState("");
  const timer = useRef();

  // Variables for throtteling
  const [throttle, setThrottle] = useState(false)


  const handleDebouncing = ({ target }) => {
    clearTimeout(timer.current)

    timer.current = setTimeout(() => {
      callApi();
    }, 300);

    setText(target.value);
  };

  const handleThrottleing = () => {
    callApi()

    setThrottle(true)

    setTimeout(() => {
      setThrottle(false)
    }, 2000)
  }

  const callApi = () => {
    console.log("Calling Api");
  };

  return (
    <div className="App">
      <input type="text" onChange={handleDebouncing} />

      <button onClick={handleThrottleing} disabled={throttle} >Click me to see throtteling</button>
    </div>
  );
}