Выполните debounce в React.js

Как вы выполняете debounce в React.js?

Я хочу разоблачить handleOnChange.

Я пробовал с debounce(this.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
    }
});

50 ответов

В 2018 году: попробуйте обещать разоблачение

Мы часто хотим отменить вызовы API, чтобы избежать затопления серверной части бесполезными запросами.

В 2018 году работа с обратными вызовами (Lodash/Underscore) чувствует себя плохо и подвержена ошибкам. Легко встретить типовые проблемы и проблемы параллелизма из-за неупорядоченных вызовов API.

Я создал небольшую библиотеку для React, чтобы решить ваши проблемы: awesome-debounce-обещание

Это не должно быть сложнее, чем это:

const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));

const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);

class SearchInputAndResults extends React.Component {
  state = {
    text: '',
    results: null,
  };

  handleTextChange = async text => {
    this.setState({ text, results: null });
    const result = await searchAPIDebounced(text);
    this.setState({ result });
  };
}

Деблокированная функция гарантирует, что:

  • вызовы API будут обсуждаться
  • Функция debounce всегда возвращает обещание
  • разрешит только возвращенное обещание последнего звонка
  • один this.setState({ result }); произойдет за вызов API

В конце концов, вы можете добавить еще один трюк, если ваш компонент размонтируется:

componentWillUnmount() {
  this.setState = () => {};
}

Обратите внимание, что Observables (RxJS) также могут отлично подходить для разбора входных данных, но это более мощная абстракция, которая может быть сложнее для правильного изучения / использования.


Все еще хотите использовать обратный вызов?

Важной частью здесь является создание единой дебазированной (или регулируемой) функции для каждого экземпляра компонента. Вы не хотите каждый раз воссоздавать функцию debounce (или throttle), и вы не хотите, чтобы несколько экземпляров использовали одну и ту же функцию debounce.

Я не определяю функцию устранения неполадок в этом ответе, так как это не очень важно, но этот ответ будет отлично работать с _.debounce подчеркивания или lodash, а также предоставляемая пользователем функция устранения неполадок.


Не хорошая идея:

var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: debounce(this.method,100);
});

Это не будет работать, потому что во время создания объекта описания класса, this это не объект, созданный сам. this.method не возвращает то, что вы ожидаете, потому что this контекст не сам объект (который на самом деле еще не существует, кстати, поскольку он только создается).


Не хорошая идея:

var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: function() {
      var debounced = debounce(this.method,100);
      debounced();
  },
});

На этот раз вы эффективно создаете опровергнутую функцию, которая вызывает ваш this.method, Проблема в том, что вы воссоздаете его на каждом debouncedMethod вызов, поэтому вновь созданная функция debounce ничего не знает о предыдущих вызовах! Вы должны повторно использовать одну и ту же отклоненную функцию с течением времени, иначе удаление не произойдет.


Не хорошая идея:

var SearchBox = React.createClass({
  debouncedMethod: debounce(function () {...},100),
});

Это немного сложно здесь.

Все подключенные экземпляры класса будут использовать одну и ту же функцию, которая была отклонена, и чаще всего это не то, что вам нужно! См. JsFiddle: 3 экземпляра производят только 1 запись журнала во всем мире.

Для каждого экземпляра компонента необходимо создать функцию debounce, а не отдельную функцию debounce на уровне класса, совместно используемую каждым экземпляром компонента.


ОТЛИЧНАЯ ИДЕЯ:

Поскольку отклоняемые функции сохраняют состояние, мы должны создать одну отклоненную функцию для каждого экземпляра компонента.

ES6 (свойство класса): рекомендуется

class SearchBox extends React.Component {
    method = debounce(() => { 
      ...
    });
}

ES6 (конструктор класса)

class SearchBox extends React.Component {
    constructor(props) {
        super(props);
        this.method = debounce(this.method,1000);
    }
    method() { ... }
}

ES5

var SearchBox = React.createClass({
    method: function() {...},
    componentWillMount: function() {
       this.method = debounce(this.method,100);
    },
});

См. JsFiddle: 3 экземпляра создают по 1 записи журнала на экземпляр (что составляет 3 глобально).


Позаботьтесь о пуле событий React

Это связано с тем, что мы часто хотим отсеивать или ограничивать события DOM.

В React объекты событий (т.е. SyntheticEvent), которые вы получаете в обратных вызовах, объединяются (теперь это задокументировано). Это означает, что после вызова обратного вызова события полученный SyntheticEvent будет помещен обратно в пул с пустыми атрибутами, чтобы уменьшить нагрузку на GC.

Поэтому, если вы обращаетесь к свойствам SyntheticEvent асинхронно с исходным обратным вызовом (как это может быть в случае, если вы дросселируете / отклоняете), свойства, к которым вы обращаетесь, могут быть удалены. Если вы хотите, чтобы событие никогда не возвращалось в пул, вы можете использовать persist() метод.

Без сохранения (поведение по умолчанию: объединенное событие)

onClick = e => {
  alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
  setTimeout(() => {
    alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
  }, 0);
};

2-й (асинхронный) напечатает hasNativeEvent=false потому что свойства события были очищены.

С упорством

onClick = e => {
  e.persist();
  alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
  setTimeout(() => {
    alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
  }, 0);
};

2-й (асинхронный) напечатает hasNativeEvent=true потому что persist() позволяет избежать возврата события в пул.

Вы можете проверить эти 2 поведения здесь JsFiddle

Прочитайте ответ Джулена для примера использования persist() с функцией дроссельной заслонки / отказов.

Неконтролируемые компоненты

Вы можете использовать event.persist() метод.

Пример следует с использованием подчеркивания _.debounce():

var SearchBox = React.createClass({

  componentWillMount: function () {
     this.delayedCallback = _.debounce(function (event) {
       // `event.target` is accessible now
     }, 1000);
  },

  onChange: function (event) {
    event.persist();
    this.delayedCallback(event);
  },

  render: function () {
    return (
      <input type="search" onChange={this.onChange} />
    );
  }

});

Редактировать: см. Это JSFiddle


Контролируемые компоненты

Обновление: в примере выше показан неконтролируемый компонент. Я использую контролируемые элементы все время, так что вот еще один пример выше, но без использования event.persist() "Обман".

JSFiddle также доступна. Пример без подчеркивания

var SearchBox = React.createClass({
    getInitialState: function () {
        return {
            query: this.props.query
        };
    },

    componentWillMount: function () {
       this.handleSearchDebounced = _.debounce(function () {
           this.props.handleSearch.apply(this, [this.state.query]);
       }, 500);
    },

    onChange: function (event) {
      this.setState({query: event.target.value});
      this.handleSearchDebounced();
    },

    render: function () {
      return (
        <input type="search"
               value={this.state.query}
               onChange={this.onChange} />
      );
    }
});


var Search = React.createClass({
    getInitialState: function () {
        return {
            result: this.props.query
        };
    },

    handleSearch: function (query) {
        this.setState({result: query});
    },

    render: function () {
      return (
        <div id="search">
          <SearchBox query={this.state.result}
                     handleSearch={this.handleSearch} />
          <p>You searched for: <strong>{this.state.result}</strong></p>
        </div>
      );
    }
});

React.render(<Search query="Initial query" />, document.body);

Изменить: обновленные примеры и JSFiddles для реагирования 0,12

Изменить: обновленные примеры для решения проблемы, поднятой Себастьеном Лорбером

Редактировать: обновлено с помощью jsfiddle, который не использует подчеркивание и использует простой отладчик javascript.

2019: Используйте хук реакции useCallback

Попробовав много разных подходов, я обнаружил, что useCallback чтобы быть самым простым и эффективным при решении проблемы множественных вызовов, используя debounce в пределах onChange событие.

Согласно документации API хуков,

useCallback возвращает запомненную версию обратного вызова, которая изменяется только при изменении одной из зависимостей.

Передача пустого массива в качестве зависимости гарантирует, что обратный вызов вызывается только один раз. Вот простая реализация:

import React, { useCallback } from "react";
import { debounce } from "lodash";

const handler = useCallback(debounce(someFunction, 2000), []);

const onChange = (event) => {
    // perform any event related action here

    handler();
 };

Надеюсь это поможет!

Поработав некоторое время с вводом текста и не найдя идеального решения самостоятельно, я нашел это на npm https://www.npmjs.com/package/react-debounce-input

Вот простой пример:

import React from 'react';
import ReactDOM from 'react-dom';
import {DebounceInput} from 'react-debounce-input';

class App extends React.Component {
state = {
    value: ''
};

render() {
    return (
    <div>
        <DebounceInput
        minLength={2}
        debounceTimeout={300}
        onChange={event => this.setState({value: event.target.value})} />

        <p>Value: {this.state.value}</p>
    </div>
    );
}
}

const appRoot = document.createElement('div');
document.body.appendChild(appRoot);
ReactDOM.render(<App />, appRoot);

Компонент DebounceInput принимает все реквизиты, которые вы можете назначить обычному элементу ввода. Попробуйте это на codepen

Я надеюсь, что это поможет кому-то еще и сэкономит им время.

Может быть простой подход с использованием реагирующих хуков.

Шаг 1: определите состояние для сохранения искомого текста

      const [searchTerm, setSearchTerm] = useState('')

Шаг 2. Используйте useEffect, чтобы зафиксировать любые изменения в поисковом запросе.

      useEffect(() => {
  const delayDebounceFn = setTimeout(() => {
    if (searchTerm) {
      // write your logic here
    }
  }, 400)

  return () => clearTimeout(delayDebounceFn)
}, [searchTerm])

Шаг 3: Напишите функцию для обработки изменения ввода

      function handleInputChange(value) {
  if (value) {
    setSearchTerm(value)
  }
}

Это все ! Вызовите этот метод по мере необходимости

Мое решение основано на крючках (написано на Typescript).

У меня есть 2 основных крючка useDebouncedValue а также useDebouncedCallback

Первый - 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

Он просто создает "противодействующую" функцию в рамках вашего компонента.

Допустим, у нас есть компонент с кнопкой, которая будет показывать предупреждение через 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;
}

2022 - используйте хук useEffect

Ваш лучший вариант в это время — использовать крючок. позволяет установить функцию, которая может изменять состояние в ответ на какое-либо асинхронное событие. Устранение дребезга является асинхронным, поэтому useEffect прекрасно подходит для этой цели.

Если вы возвращаете функцию из хука, возвращаемая функция будет вызвана до того, как хук будет вызван снова. Это позволяет отменить предыдущий тайм-аут, эффективно устраняя дребезг функции.

Пример

Здесь у нас есть два состояния, и . Настройка вызоветuseEffectловушка, которая запустит тайм-аут 1000 мс, который вызовет функцию для копированияtempValueвvalue.

Хук возвращает функцию, которая сбрасывает таймер. Когда хук вызывается снова (т.е. нажимается другая клавиша), тайм-аут отменяется и сбрасывается.

      const DebounceDemo = () => {
  const [value, setValue] = useState();
  const [tempValue, setTempValue] = useState();

  // This hook will set a 1000 ms timer to copy tempValue into value
  // If the hook is called again, the timer will be cancelled
  // This creates a debounce
  useEffect(
    () => {
      // Wait 1000ms before copying the value of tempValue into value;
      const timeout = setTimeout(() => {
        setValue(tempValue);
      }, 1000);

      // If the hook is called again, cancel the previous timeout
      // This creates a debounce instead of a delay
      return () => clearTimeout(timeout);
    },
    // Run the hook every time the user makes a keystroke
    [tempValue]
  )

  // Here we create an input to set tempValue. 
  // value will be updated 1000 ms after the hook is last called, 
  // i.e after the last user keystroke.
  return (
    <>
      <input 
        onChange={
          ({ target }) => setTempValue(target.value)
        }
      />
      <p>{ value }</p>
    </>
  )
}

Я нашел этот пост Джастином Тулком очень полезным. После нескольких попыток, что, как можно было бы воспринимать как более официальный способ с реакцией / редукцией, показывает, что он терпит неудачу из-за искусственного объединения событий в React. Затем его решение использует некоторое внутреннее состояние для отслеживания значения, измененного / введенного во вход, с обратным вызовом сразу после setState который вызывает удушенное / дебилируемое избыточное действие, которое показывает некоторые результаты в реальном времени.

import React, {Component} from 'react'
import TextField from 'material-ui/TextField'
import { debounce } from 'lodash'

class TableSearch extends Component {

  constructor(props){
    super(props)

    this.state = {
        value: props.value
    }

    this.changeSearch = debounce(this.props.changeSearch, 250)
  }

  handleChange = (e) => {
    const val = e.target.value

    this.setState({ value: val }, () => {
      this.changeSearch(val)
    })
  }

  render() {

    return (
        <TextField
            className = {styles.field}
            onChange = {this.handleChange}
            value = {this.props.value}
        />
    )
  }
}

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

Справочная таблица - https://gist.github.com/elijahmanor/08fc6c8468c994c844213e4a4344a709

Если все, что вам нужно от объекта события, - это получить элемент ввода DOM, решение будет намного проще - просто используйте ref

class Item extends React.Component {
    constructor(props) {
        super(props);
        this.saveTitle = _.throttle(this.saveTitle.bind(this), 1000);
    }
    saveTitle(){
        let val = this.inputTitle.value;
        // make the ajax call
    }
    render() {
        return <input 
                    ref={ el => this.inputTitle = el } 
                    type="text" 
                    defaultValue={this.props.title} 
                    onChange={this.saveTitle} />
    }
}

Есть use-debounce пакет, который можно использовать с хуками ReactJS.

Из README пакета:

import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000);

  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

Как видно из приведенного выше примера, он настроен на обновление переменной value только раз в секунду (1000 миллисекунд).

Если вы используете Redux, вы можете сделать это очень элегантно с промежуточным программным обеспечением. Вы можете определить Debounce промежуточное ПО как:

var timeout;
export default store => next => action => {
  const { meta = {} } = action;
  if(meta.debounce){
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      next(action)
    }, meta.debounce)
  }else{
    next(action)
  }
}

Затем вы можете добавить debouncing для создателей действий, таких как:

export default debouncedAction = (payload) => ({
  type : 'DEBOUNCED_ACTION',
  payload : payload,
  meta : {debounce : 300}
}

На самом деле уже есть промежуточное ПО, которое вы можете отключить, чтобы сделать это за вас.

Здесь много полезной информации, но, чтобы быть кратким. Это работает для меня...

import React, {Component} from 'react';
import _ from 'lodash';

class MyComponent extends Component{
      constructor(props){
        super(props);
        this.handleChange = _.debounce(this.handleChange.bind(this),700);
      }; 

Использование ES6 CLASS и React 15.xx & lodash.debounce Im с использованием ссылок React здесь, поскольку событие теряет внутреннюю привязку this.

class UserInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      userInput: ""
    };
    this.updateInput = _.debounce(this.updateInput, 500);
  }


  updateInput(userInput) {
    this.setState({
      userInput
    });
    //OrderActions.updateValue(userInput);//do some server stuff
  }


  render() {
    return ( <div>
      <p> User typed: {
        this.state.userInput
      } </p>
      <input ref = "userValue" onChange = {() => this.updateInput(this.refs.userValue.value) } type = "text" / >
      </div>
    );
  }
}

ReactDOM.render( <
  UserInput / > ,
  document.getElementById('root')
);
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.5/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


<div id="root"></div>

Вы можете использовать метод Lodash debounce https://lodash.com/docs/4.17.5#debounce. Это просто и эффективно.

import * as lodash from lodash;

const update = (input) => {
    // Update the input here.
    console.log(`Input ${input}`);     
}

const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200});

doHandleChange() {
   debounceHandleUpdate(input);
}

Вы также можете отменить метод debounce, используя метод ниже.

this.debounceHandleUpdate.cancel();

Надеюсь, это поможет вам. Ура!!

FYI

Вот еще одна реализация PoC:

  • без каких-либо библиотек (например, lodash) для разбора
  • используя React Hooks API

Я надеюсь, что это помогает:)

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';

function debounced(func, wait) {
  let timeout;
  return (...args) => {
    if (timeout) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(() => {
      func(args);
    }, wait);
  };
}

export default function DebouncedSearchBox(props) {
  const [query, setQuery] = useState('');

  useEffect(() => {
    debounced(() => {
      props.handleSearch(query);
    }, props.debounceInterval)();
  })

  const handleOnChange = (evt) => {
    setQuery(evt.target.value);
  }

  return (
    <input
      type="text"
      placeholder="name or email"
      value={query}
      onChange={handleOnChange}
    />
  );
}

DebouncedSearchBox.propTypes = {
  handleSearch: PropTypes.func.isRequired,
  debounceInterval: PropTypes.number.isRequired,
}

Я не могу найти никаких ответов по этому вопросу, в которых упоминается подход, который я использую, поэтому просто хочу предоставить здесь альтернативное решение, которое я считаю лучшим для моего варианта использования.

Если вы используете популярную библиотеку инструментария реагирующих перехватчиков, которая называется react-use, то есть служебный перехватчик, называемый useDebounce() это реализовало логику денонсирования довольно элегантным способом.

      const [query, setQuery] = useState('');

useDebounce(
  () => {
    emitYourOnDebouncedSearchEvent(query);
  },
  2000,
  [query]
);

return <input onChange={({ currentTarget }) => setQuery(currentTarget.value)} />

Для получения подробной информации, пожалуйста, проверьте страницу библиотеки на github напрямую.

https://github.com/streamich/react-use/blob/master/docs/useDebounce.md

В конце 2019 года есть еще одно решение для React и React Native:

реагировать-противодействовать-компонент

<input>
<Debounce ms={500}>
  <List/>
</Debounce>

Это компонент, простой в использовании, крошечный и широко поддерживаемый.

Пример:

import React from 'react';
import Debounce from 'react-debounce-component';

class App extends React.Component {
  constructor (props) {
    super(props);
    this.state = {value: 'Hello'}
  }
  render () {
    return (
      <div>
        <input value={this.state.value} onChange={(event) => {this.setState({value: event.target.value})}}/>
        <Debounce ms={1000}>
          <div>{this.state.value}</div>
        </Debounce>
      </div>
    );
  }
}

export default App;

* Я создатель этого компонента

Что касается июня 2021 года, вы можете просто реализовать решение 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;
}

Ты пробовал?

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) {
    debounce(\\ Your handleChange code , 200);
  }
});

Красивое и чистое решение, не требующее никаких внешних зависимостей:

Устранение ошибок с помощью хуков React

Он использует custom, а также хуки useEffect React и setTimeout / clearTimeout метод.

Просто еще один вариант с недавними реакциями и лодашами.

class Filter extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired
  }

  state = {
    initialText: '',
    text: ''
  }

  constructor (props) {
    super(props)

    this.setText = this.setText.bind(this)
    this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
  }

  static getDerivedStateFromProps (nextProps, prevState) {
    const { text } = nextProps

    if (text !== prevState.initialText) {
      return { initialText: text, text }
    }

    return null
  }

  setText (text) {
    this.setState({ text })
    this.onChange(text)
  }

  onChange (text) {
    this.props.onChange(text)
  }

  render () {
    return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
  }
}

Это решение не требует дополнительной библиотеки, и оно также запускается, когда пользователь нажимает Enter:

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

const [search, setSearch] = useState('');
const [searchFor, setSearchFor] = useState(search);

useEffect(() => {
    console.log("Search:", searchFor);
}, [searchFor]);

const fireChange = event => {
    const { keyCode } = event;
    if (keyCode === 13) {
        event.preventDefault();
        setSearchFor(search);
    }
}

const changeSearch = event => {
    const { value } = event.target;
    setSearch(value);
    debounceSetSearchFor(value);
};

const debounceSetSearchFor = useCallback(debounce(function(value) {
    setSearchFor(value);
}, 250), []);

и ввод может быть таким:

<input value={search} onKeyDown={fireChange} onChange={changeSearch}  />

простой и эффективный
https://www.npmjs.com/package/use-debounce
use-debounce

      import { useDebouncedCallback } from 'use-debounce';

function Input({ defaultValue }) {
  const [value, setValue] = useState(defaultValue);
  const debounced = useDebouncedCallback(
    (value) => {
      setValue(value);
    },
    // delay
    1000
  );

  return (
    <div>
      <input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />
      <p>Debounced value: {value}</p>
    </div>
  );
}

Вот фрагмент, использующий подход @Abra, заключенный в функциональный компонент (мы используем ткань для пользовательского интерфейса, просто замените ее простой кнопкой)

import React, { useCallback } from "react";
import { debounce } from "lodash";

import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';

const debounceTimeInMS = 2000;

export const PrimaryButtonDebounced = (props) => {

    const debouncedOnClick = debounce(props.onClick, debounceTimeInMS, { leading: true });

    const clickHandlerDebounced = useCallback((e, value) => {

        debouncedOnClick(e, value);

    },[]);

    const onClick = (e, value) => {

        clickHandlerDebounced(e, value);
    };

    return (
        <PrimaryButton {...props}
            onClick={onClick}
        />
    );
}

Крючок :

      import {useState} from "react";

const useDebounce = ({defaultTimeout = 250, defaultIdentifier = 'default'} = {}) => {

    const [identifiers, setIdentifiers] = useState({[defaultIdentifier]: null});

    return ({fn = null, identifier = defaultIdentifier, timeout = defaultTimeout} = {}) => {
        if (identifiers.hasOwnProperty(identifier)) clearTimeout(identifiers[identifier]);
        setIdentifiers({...identifiers, [identifier]: setTimeout(fn, timeout)});
    };
};

export default useDebounce;

И используйте его где угодно (в том же файле используйте идентификатор, чтобы предотвратить совпадение), например:

      const debounce = useDebounce();

const handlerA = () => {
    debounce({fn: () => console.log('after 2000ms of last call with identifier A'), identifier: 'A', timeout: 2000});
};

const handlerB = () => {
    debounce({fn: () => console.log('after 1500ms of last call with identifier B'), identifier: 'B', timeout: 1500});
};

Вместо того, чтобы оборачивать handleOnChange в debounce(), почему бы не обернуть вызов ajax внутри функции обратного вызова внутри debounce, тем самым не уничтожив объект события. Так что-то вроде этого:

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

Вот пример, который я придумал, который оборачивает другой класс с помощью debouncer. Это прекрасно подходит для превращения в функцию декоратора / высшего порядка:

export class DebouncedThingy extends React.Component {
    static ToDebounce = ['someProp', 'someProp2'];
    constructor(props) {
        super(props);
        this.state = {};
    }
    // On prop maybe changed
    componentWillReceiveProps = (nextProps) => {
        this.debouncedSetState();
    };
    // Before initial render
    componentWillMount = () => {
        // Set state then debounce it from here on out (consider using _.throttle)
        this.debouncedSetState();
        this.debouncedSetState = _.debounce(this.debouncedSetState, 300);
    };
    debouncedSetState = () => {
        this.setState(_.pick(this.props, DebouncedThingy.ToDebounce));
    };
    render() {
        const restOfProps = _.omit(this.props, DebouncedThingy.ToDebounce);
        return <Thingy {...restOfProps} {...this.state} />
    }
}

Сегодня встретил эту проблему. Решил с помощьюsetTimeout а также clearTimeout.

Приведу пример, который вы могли бы адаптировать:

import React, { Component } from 'react'

const DEBOUNCE_TIME = 500

class PlacesAutocomplete extends Component {
  debounceTimer = null;

  onChangeHandler = (event) => {
    // Clear the last registered timer for the function
    clearTimeout(this.debounceTimer);

    // Set a new timer
    this.debounceTimer = setTimeout(
      // Bind the callback function to pass the current input value as arg
      this.getSuggestions.bind(null, event.target.value), 
      DEBOUNCE_TIME
    )
  }

  // The function that is being debounced
  getSuggestions = (searchTerm) => {
    console.log(searchTerm)
  }

  render() {
    return (
      <input type="text" onChange={this.onChangeHandler} />
    )
  }
}

export default PlacesAutocomplete

Вы также можете провести рефакторинг в собственном функциональном компоненте:

import React from 'react'

function DebouncedInput({ debounceTime, callback}) {
  let debounceTimer = null
  return (
    <input type="text" onChange={(event) => {
      clearTimeout(debounceTimer);

      debounceTimer = setTimeout(
        callback.bind(null, event.target.value), 
        debounceTime
      )
    }} />
  )
}

export default DebouncedInput

И используйте это так:

import React, { Component } from 'react'
import DebouncedInput from '../DebouncedInput';

class PlacesAutocomplete extends Component {
  debounceTimer = null;

  getSuggestions = (searchTerm) => {
    console.log(searchTerm)
  }

  render() {
    return (
      <DebouncedInput debounceTime={500} callback={this.getSuggestions} />
    )
  }
}

export default PlacesAutocomplete

Если вам просто нужно выполнить отладку кнопки для запроса данных, предоставленный код может быть вам полезен:

  1. Создайте функцию для предотвращения использования по умолчанию с условным оператором, если запрос истинен или ложен

  2. Реализовать Крюк useState и useEffect Hook

const PageOne = () => {
 const [requesting, setRequesting] = useState(false);

  useEffect(() => {
    return () => {
      setRequesting(false);
    };
  }, [requesting]);

  const onDebounce = (e) => {
    if (requesting === true) {
      e.preventDefault();
    }
    // ACTIONS
    setLoading(true);
  };

 return (
  <div>
    
    <button onClick={onDebounce}>Requesting data</button>
  </div>
 )
}

Другие вопросы по тегам