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


497

Как вы выполняете 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
  }
});

Я встретил ту же проблему с вами, превосходные ответы ниже! но я думаю, что вы использовали неправильный способ debounce. здесь, когда onChange={debounce(this.handleOnChange, 200)}/>он будет вызывать debounce functionкаждый раз. но на самом деле нам нужно вызвать функцию, возвращаемую функцией debounce.
pingfengafei

Ответы:


835

2019: попробовать крючки + обещание разоблачения

Это самая актуальная версия того, как бы я решил эту проблему. Я хотел бы использовать:

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

// Generic reusable hook
const useDebouncedSearch = (searchFunction) => {

  // Handle the input text state
  const [inputText, setInputText] = useState('');

  // Debounce the original search async function
  const debouncedSearchFunction = useConstant(() =>
    AwesomeDebouncePromise(searchFunction, 300)
  );

  // The async callback is run each time the text changes,
  // but as the search function is debounced, it does not
  // fire a new request on each keystroke
  const searchResults = useAsync(
    async () => {
      if (inputText.length === 0) {
        return [];
      } else {
        return debouncedSearchFunction(inputText);
      }
    },
    [debouncedSearchFunction, inputText]
  );

  // Return everything needed for the hook consumer
  return {
    inputText,
    setInputText,
    searchResults,
  };
};

И тогда вы можете использовать свой крючок:

const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))

const SearchStarwarsHeroExample = () => {
  const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
  return (
    <div>
      <input value={inputText} onChange={e => setInputText(e.target.value)} />
      <div>
        {searchResults.loading && <div>...</div>}
        {searchResults.error && <div>Error: {search.error.message}</div>}
        {searchResults.result && (
          <div>
            <div>Results: {search.result.length}</div>
            <ul>
              {searchResults.result.map(hero => (
                <li key={hero.name}>{hero.name}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  );
};

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


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 будут отклонены
  • дебагованная функция всегда возвращает обещание
  • разрешит только возвращенное обещание последнего звонка
  • один this.setState({ result });вызов произойдет за вызов API

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

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

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


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

Важной частью здесь является создание единой дебазированной (или регулируемой) функции для каждого экземпляра компонента . Вы не хотите каждый раз воссоздавать функцию debounce (или throttle), и вы не хотите, чтобы несколько экземпляров использовали одну и ту же функцию 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.bind(this),1000);
    }
    method() { ... }
}

ES5

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

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


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

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 запись журнала во всем мире.

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


Позаботьтесь о пуле событий 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()с функцией throttle / debounce.


3
Превосходный ответ, это отлично подходит для установки состояния полей формы как «взаимодействующего» в течение нескольких секунд после того, как они перестают печатать, и затем возможности отмены при отправке формы или onBlur
arush_try.com

8
Обратите внимание, что в ES6 вместо определения вашего метода внутри конструктора (кажется странным) вы можете сделать это handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)на верхнем уровне вашего класса. Вы по-прежнему эффективно устанавливаете элемент экземпляра, но это выглядит немного больше как обычное определение метода. Нет необходимости, constructorесли у вас его еще нет. Я полагаю, это в основном стиль предпочтений.
thom_nic

24
Не забудьте отменить метод debounce в componentWillUnmount: this.method.cancel()- в противном случае он может захотеть установить setState для несмонтированного компонента.
elado

4
@JonasKello, вы не можете дебатировать внутри компонента без состояния, потому что дебагованная функция на самом деле с состоянием. Вам нужен компонент с состоянием для хранения этой отклоненной функции, но при необходимости вы можете вызвать компонент без состояния с уже отклоненной функцией.
Себастьян Лорбер,

2
Почему весь ответ включает _.debounce вместо написания функции? Для этой функции нужна целая библиотека?
Chifliiiii

217

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

Вы можете использовать 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.


Это не работает для входов. Цель события в дебазированной функции больше не имеет значения ... поэтому вход остается пустым.
Этай

1
Немного сложный, это. Вы должны быть немного осторожны с реквизитом. Если вы установите <input value={this.props.someprop}...его, он не будет отображаться должным образом, так как обновление по нажатию клавиши не вернет его обратно в компонент до окончания отладки. Можно не value=указывать, если вы рады, что это неуправляемо, но если вы хотите предварительно заполнить значение и / или связать его где-то еще, тогда, очевидно, это не сработает.
Аластер Мо

1
@AlastairMaw у вопроса был неконтролируемый компонент, поэтому в ответе он тоже есть. Ниже я добавил альтернативную версию для контролируемых компонентов с предварительно заполненным значением.
июля

2
это очень опасно, если вы монтируете компонент несколько раз в DOM, см. stackoverflow.com/questions/23123138/…
Себастьен Лорбер

4
хотя это отличный ответ, я не рекомендую использовать его, persistособенно когда может быть много событий, например mousemove. Я видел, как код стал полностью безразличным таким образом. Гораздо эффективнее извлечь необходимые данные из собственного события в вызове события, а затем вызвать функцию debounce / throttled только с данными, а не с самим событием. Не нужно так настаивать на этом событии
Мистер

31

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

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

Согласно документации Hooks 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();
 };

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


3
Отличное решение, если вы используете крючки. Вы спасли меня еще много часов разочарования. Спасибо!
Карл Эдвардс

Не могли бы вы объяснить, почему множественные звонки происходят в первую очередь? Не debounce()считает ли onChange()обратный вызов тем же методом обратного вызова?
Эль

Я изменил это решение, чтобы оно работало в моем приложении. Сначала я должен был переместить строку const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);внутри тела компонента функции или React выдает сообщение об ошибке использования крючка за ее пределами. Тогда в onChangeобработчик событий: <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.
Эль

Вот как я использовал это решение, чтобы позволить пользователю вводить данные для ввода, а затем отправлять опровергнутый вызов API со значением ввода, как только он закончит ввод. stackoverflow.com/questions/59358092/… .
Эль

14

Я нашел этот пост Джастином Тулком очень полезным. После нескольких попыток, что, как можно было бы воспринимать как более официальный способ с реакцией / редукцией, показывает, что он терпит неудачу из-за искусственного объединения событий в 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}
        />
    )
  }
}

14

Если все, что вам нужно от объекта события, - это получить элемент ввода 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} />
    }
}

2
defaultValue - это то, что я хочу! Спасибо, Маха :)
Тазо Леладзе

14

Поработав некоторое время с текстовыми вводами и не найдя идеального решения самостоятельно, я нашел это на npm: реагировать-отсеивать-вводить .

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

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

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


Попробовав множество решений, перечисленных здесь, определенно оказался самым простым.
Vadorequest

9

С 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


1
отлично работает лучшая реализация, которую я нашел до сих пор
Винсент Тан

8

Если вы используете 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}
}

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


я думаю, что это промежуточное ПО должно быть первым, которое будет выполнено в applyMiddleware(...)цепочке, если у нас их много
Юсеф

Тайм-аут не инициализирован, и первый clearTimeout будет иметь дело с неопределенным параметром. Фигово.
Джейсон Райс

7

Использование ES6 CLASS и React 15.xx & lodash.debounce Im используя РЕАКТ рефов здесь , поскольку потери Ивентов это связывают внутренне.

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>


7

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

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

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

Это не работает для меня. Состояние не обновляется. Если я удаляю _debounce обертку, это работает. Я люблю эту идею, хотя!
Mote

Мне бы хотелось увидеть ваш код, чтобы многое предложить здесь, но я подозреваю, что происходит что-то еще ... надеюсь, этот гораздо более тщательный ответ пролил некоторый свет. stackoverflow.com/questions/23123138/...
чад Steele

6

Вы можете использовать метод отката Lodash 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();

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


5

FYI

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

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

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

import React, { useState, useEffect, ChangeEvent } from 'react';

export default function DebouncedSearchBox({
  inputType,
  handleSearch,
  placeholder,
  debounceInterval,
}: {
  inputType?: string;
  handleSearch: (q: string) => void;
  placeholder: string;
  debounceInterval: number;
}) {
  const [query, setQuery] = useState<string>('');
  const [timer, setTimer] = useState<NodeJS.Timer | undefined>();

  useEffect(() => {
    if (timer) {
      clearTimeout(timer);
    }
    setTimer(setTimeout(() => {
      handleSearch(query);
    }, debounceInterval));
  }, [query]);

  const handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
    setQuery(e.target.value);
  };

  return (
    <input
      type={inputType || 'text'}
      className="form-control"
      placeholder={placeholder}
      value={query}
      onChange={handleOnChange}
    />
  );
}

4

Есть 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 миллисекунд).


3

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

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)} />)
  }
}


3

Ты пробовал?

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);
  }
});

2

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

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

4
Поскольку объект события не является неизменным и уничтожается с помощью ReactJS, поэтому даже если вы обернетесь и достигнете захвата замыкания, код потерпит неудачу.
Хенрик

2

Вот пример, который я придумал, который связывает другой класс с 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} />
    }
}

2

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

реагирует-дребезг-компонентный

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

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

Пример:

введите описание изображения здесь

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;

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


1

Я искал решение той же проблемы и наткнулся на этот поток, а также на некоторые другие, но у них была та же проблема: если вы пытаетесь выполнить handleOnChangeфункцию и вам нужно значение из целевого объекта события, вы получите cannot read property value of nullили несколько такая ошибка. В моем случае мне также нужно было сохранить контекст thisвнутри обсуждаемой функции, так как я выполняю гибкое действие. Вот мое решение, оно хорошо работает для моего варианта использования, поэтому я оставляю его здесь на случай, если кто-нибудь натолкнется на эту тему:

// at top of file:
var myAction = require('../actions/someAction');

// inside React.createClass({...});

handleOnChange: function (event) {
    var value = event.target.value;
    var doAction = _.curry(this.context.executeAction, 2);

    // only one parameter gets passed into the curried function,
    // so the function passed as the first parameter to _.curry()
    // will not be executed until the second parameter is passed
    // which happens in the next function that is wrapped in _.debounce()
    debouncedOnChange(doAction(myAction), value);
},

debouncedOnChange: _.debounce(function(action, value) {
    action(value);
}, 300)

1

за throttle или debounceлучший способ - создать создателя функции, чтобы вы могли использовать его где угодно, например:

  updateUserProfileField(fieldName) {
    const handler = throttle(value => {
      console.log(fieldName, value);
    }, 400);
    return evt => handler(evt.target.value.trim());
  }

и в вашем render методе вы можете сделать:

<input onChange={this.updateUserProfileField("givenName").bind(this)}/>

updateUserProfileFieldметод создаст Отделимую функцию каждый раз , когда вы называете его.

Примечание: не пытайтесь вернуть обработчик напрямую, например, это не будет работать:

 updateUserProfileField(fieldName) {
    return evt => throttle(value => {
      console.log(fieldName, value);
    }, 400)(evt.target.value.trim());
  }

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

Кроме того, если вы используете, debounceили throttleвам не нужно, setTimeoutили clearTimeoutэто то, почему мы используем их: P


1

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

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}
        />
    );
}

1

Мое решение основано на хуках (написано в 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;
}

0

Вот рабочий пример TypeScript для тех, кто использует TS и хочет отменить asyncфункции.

function debounce<T extends (...args: any[]) => any>(time: number, func: T): (...funcArgs: Parameters<T>) => Promise<ReturnType<T>> {
     let timeout: Timeout;

     return (...args: Parameters<T>): Promise<ReturnType<T>> => new Promise((resolve) => {
         clearTimeout(timeout);
         timeout = setTimeout(() => {
             resolve(func(...args));
         }, time)
     });
 }

0

немного поздно здесь, но это должно помочь. создать этот класс (он написан на машинописном тексте, но его легко конвертировать в javascript)

export class debouncedMethod<T>{
  constructor(method:T, debounceTime:number){
    this._method = method;
    this._debounceTime = debounceTime;
  }
  private _method:T;
  private _timeout:number;
  private _debounceTime:number;
  public invoke:T = ((...args:any[])=>{
    this._timeout && window.clearTimeout(this._timeout);
    this._timeout = window.setTimeout(()=>{
      (this._method as any)(...args);
    },this._debounceTime);
  }) as any;
}

и использовать

var foo = new debouncedMethod((name,age)=>{
 console.log(name,age);
},500);
foo.invoke("john",31);

0

Вы можете использовать Tlence Tlence

function log(server) {
  console.log('connecting to', server);
}

const debounceLog = debounce(log, 5000);
// just run last call to 5s
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');
debounceLog('local');

0

Решение Julen довольно сложно прочитать, здесь приведен более понятный и точный код реагирования для любого, кто наткнулся на него, основываясь на названии, а не на крошечных деталях вопроса.

tl; dr version : когда вы обновляете наблюдателей, вместо этого отправляйте метод вызова по расписанию, который, в свою очередь, будет фактически уведомлять наблюдателей (или выполнять ajax и т. д.)

Завершите jsfiddle с примером компонента jsfiddle

var InputField = React.createClass({

    getDefaultProps: function () {
        return {
            initialValue: '',
            onChange: null
        };
    },

    getInitialState: function () {
        return {
            value: this.props.initialValue
        };
    },

    render: function () {
        var state = this.state;
        return (
            <input type="text"
                   value={state.value}
                   onChange={this.onVolatileChange} />
        );
    },

    onVolatileChange: function (event) {
        this.setState({ 
            value: event.target.value 
        });

        this.scheduleChange();
    },

    scheduleChange: _.debounce(function () {
        this.onChange();
    }, 250),

    onChange: function () {
        var props = this.props;
        if (props.onChange != null) {
            props.onChange.call(this, this.state.value)
        }
    },

});

3
Разве это не сделает состояние / время отклика глобальным для всех экземпляров InputField, потому что он создан с определением класса? Может быть, это то, что вы хотите, но это стоит отметить в любом случае.
грабит

1
опасно, если монтируется несколько раз в доме, проверьте stackoverflow.com/questions/23123138/…
Себастьен Лорбер

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

0

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

  1. Обратный звонок без устранения
  2. Вызывает отклоненную функцию только с теми частями события, которые вам нужны (чтобы синтетическое событие могло быть переработано)

Классы

handleMouseOver = throttle(target => {
  console.log(target);
}, 1000);

onMouseOver = e => {
  this.handleMouseOver(e.target);
};

<div onMouseOver={this.onMouseOver} />

функции

const handleMouseOver = useRef(throttle(target => {
  console.log(target);
}, 1000));

function onMouseOver(e) {
  handleMouseOver.current(e.target);
}

<div onMouseOver={this.onMouseOver} />

Обратите внимание , что если ваша handleMouseOverфункция использует состояние внутри компонента, вы должны использовать useMemoвместо useRefи передавать их в качестве зависимостей иначе вы будете работать с устаревшими данными (не относится к классам, конечно).


0

Расширить useState hook

import { useState } from "react";
import _ from "underscore"
export const useDebouncedState = (initialState, durationInMs = 500) => {
    const [internalState, setInternalState] = useState(initialState);
    const debouncedFunction = _.debounce(setInternalState, durationInMs);
    return [internalState, debouncedFunction];
};
export default useDebouncedState;

Использовать крюк

import useDebouncedState from "../hooks/useDebouncedState"
//...
const [usernameFilter, setUsernameFilter] = useDebouncedState("")
//...
<input id="username" type="text" onChange={e => setUsernameFilter(e.target.value)}></input>

https://trippingoncode.com/react-debounce-hook/


0

Встретил эту проблему сегодня. Решил это используя 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.