Выполните 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
  }
});

Ответ 1

2019: попробуйте зацепки + дебасинг обещания

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

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

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

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

  const search = useAsync(
    async () => {
      if (inputText.length === 0) {
        return [];
      } else {
        return debouncedSearchStarwarsHero(inputText);
      }
    },
    // Ensure a new request is made everytime the text changes (even if it debounced)
    [inputText]
  );

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

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

const SearchStarwarsHeroExample = () => {
  const { inputText, setInputText, search } = useSearchStarwarsHero();
  return (
    <div>
      <input value={inputText} onChange={e => setInputText(e.target.value)} />
      <div>
        {search.loading && <div>...</div>}
        {search.error && <div>Error: {search.error.message}</div>}
        {search.result && (
          <div>
            <div>Results: {search.result.length}</div>
            <ul>
              {search.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 будут объявлены
  • функция debounce всегда возвращает обещание
  • разрешит только обещание, которое вернул последний звонок
  • один вызов this.setState({ result }); произойдет на вызов API

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

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

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


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

2nd (async) напечатает 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() с функцией дроссельной заслонки/отказов.

Ответ 2

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

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

A Также доступен 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-debounce.

Ответ 3

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

Ответ 4

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

Ответ 5

Поработав некоторое время с вводом текста и не найдя идеального решения самостоятельно, я нашел это на npm: response-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

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

Ответ 6

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

Там на самом деле уже есть промежуточное ПО, вы можете выйти из npm, чтобы сделать это для вас.

Ответ 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);
      }; 

Ответ 8

Использование 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/[email protected]/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>

Ответ 9

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

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

Ответ 10

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

Ответ 11

С помощью 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 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

Ответ 12

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

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

Ответ 13

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

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

Ответ 14

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

Ответ 15

Хорошее и чистое решение, которое не требует каких-либо внешних зависимостей:

Дебактинг с помощью React Hooks

Он использует пользовательский плюс хуки useEffect React и метод setTimeout/clearTimeout.

Ответ 16

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

Ответ 17

Я искал решение одной и той же проблемы и наткнулся на этот поток, а также на некоторые другие, но у них была одна и та же проблема: если вы пытаетесь выполнить функцию 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)

Ответ 18

для 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

Ответ 19

Решение 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)
        }
    },

});

Ответ 20

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

Ответ 21

Вот рабочий пример 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)
     });
 }

Ответ 22

немного поздно здесь, но это должно помочь. создать этот класс (он написан на машинописном тексте, но его легко конвертировать в 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);

Ответ 23

Избегайте использования 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 и передавать их как зависимости, иначе вы будете работать с устаревшими данными (не относится к классам, конечно).

Ответ 24

Вы также можете использовать самоналоженный микшинг, что-то вроде этого:

var DebounceMixin = {
  debounce: function(func, time, immediate) {
    var timeout = this.debouncedTimeout;
    if (!timeout) {
      if (immediate) func();
      this.debouncedTimeout = setTimeout(function() {
        if (!immediate) func();
        this.debouncedTimeout = void 0;
      }.bind(this), time);
    }
  }
};

И затем используйте его в своем компоненте следующим образом:

var MyComponent = React.createClass({
  mixins: [DebounceMixin],
  handleClick: function(e) {
    this.debounce(function() {
      this.setState({
        buttonClicked: true
      });
    }.bind(this), 500, true);
  },
  render: function() {
    return (
      <button onClick={this.handleClick}></button>
    );
  }
});

Ответ 25

Не нужно тонны локальных переменных для приличной функции газа. Целью функции регулирования является сокращение ресурсов браузера, а не применение таких дополнительных затрат, которые вы используете еще больше. В качестве доказательства этого утверждения я разработал дроссельную функцию, которая имеет только 4 "зависающие" переменные. (Переменная "зависания" - это переменная, которая никогда не собирается сборщиком мусора, потому что на нее всегда ссылается функция, которая потенциально может быть вызвана, тем самым впитывая память.) Горстка дроссельных функций обычно не приносит никакого вреда; но, если есть тысячи дросселированных функций, то памяти становится мало, если вы используете действительно неэффективную функцию дросселя. Мое решение ниже.

var timenow=self.performance ? performance.now.bind(performance) : Date.now;
function throttle(func, alternateFunc, minInterval) {
    var lastTimeWent = -minInterval, freshArguments=null;
    function executeLater(){
        func.apply(null, freshArguments);
        freshArguments = null;
        lastTimeWent = 0;
    }
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(null, arguments);
        } else {
            if (freshArguments === null)
                setTimeout(executeLater, minInterval-(newTimeWent-lastTimeWent));
            freshArguments = arguments;
            if (typeof alternateFunc === "function")
                return alternateFunc.apply(null, arguments);
        }
    };
}

Затем, чтобы обернуть эту функцию регулирования вокруг EventTarget для таких вещей, как щелчки DOM, события окна, XMLHttpRequests onprogress, FileReader onprogress и т.д., Например, так:

var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
    var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (tfCache[i] === func &&
              tfCache[i+1] === (options.ALTERNATE||null) &&
              tfCache[i+2] === (options.INTERVAL||200)
            ) break a;
        cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
        tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
    }
    source.addEventListener(eventName, cF || tfCache[i+3], _opts);
    return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
    var options = _opts || {};
    for (var i = 0, Len = tfCache.length; i < Len; i += 4)
        if (tfCache[i] === func &&
          tfCache[i+1] === (options.ALTERNATE||null) &&
          tfCache[i+2] === (options.INTERVAL||200)
        ) {
            source.removeEventListener(eventName, tfCache[i+3], options);
            return true;
        }
    return false;
}

Ниже приведен пример нажатия кнопки для прослушивания щелчка только один раз в секунду. Когда он получает этот щелчок, он меняется на новый случайный цвет.

(function(){"use strict";
// The function throttler //
var timenow=self.performance ? performance.now.bind(performance) : Date.now;
function throttle(func, alternateFunc, minInterval) {
    var lastTimeWent = -minInterval, freshArguments=null;
    function executeLater(){
        func.apply(null, freshArguments);
        freshArguments = null;
        lastTimeWent = 0;
    }
    return function() {
        var newTimeWent = timenow();
        if ((newTimeWent-lastTimeWent) > minInterval) {
            lastTimeWent = newTimeWent;
            return func.apply(null, arguments);
        } else {
            if (freshArguments === null)
                setTimeout(executeLater,minInterval-(newTimeWent-lastTimeWent));
            freshArguments = arguments;
            if (typeof alternateFunc === "function")
                return alternateFunc.apply(this, arguments);
        }
    };
}
// The EventTarget wrapper: //
var tfCache = []; // throttled functions cache
function listen(source, eventName, func, _opts){
    var i = 0, Len = tfCache.length, cF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (tfCache[i] === func &&
              tfCache[i+1] === (options.ALTERNATE||null) &&
              tfCache[i+2] === (options.INTERVAL||200)
            ) break a;
        cF = throttle(func, options.ALTERNATE||null, options.INTERVAL||200);
        tfCache.push(func, options.ALTERNATE||null, options.INTERVAL||200, cF);
    }
    source.addEventListener(eventName, cF || tfCache[i+3], _opts);
    return cF === null; // return whether it used the cache or not
};
function mute(source, eventName, func, _opts){
    var options = _opts || {};
    for (var i = 0, Len = tfCache.length; i < Len; i += 4)
        if (tfCache[i] === func &&
          tfCache[i+1] === (options.ALTERNATE||null) &&
          tfCache[i+2] === (options.INTERVAL||200)
        ) {
            source.removeEventListener(eventName, tfCache[i+3], options);
            return true;
        }
    return false;
}
// Finally, the color changing button: //
function randHex(){ // weighted towards the ends of the scales for contrast
    var rand = Math.random()*2 - 1; // equally offcenter it from one
    var sign = rand < 0 ? -1 : 1; // get a sign-ish value
    rand = Math.sqrt(rand * sign) * sign; // stretch it further from zero
    rand = 128 + rand * 128; // next, recenter it to range from 0 to 255 
    var str = (rand | 0).toString(16); // make an integer string
    while (str.length < 2) str = "0" + str; // pad it
    return str; // finally, return it
}
var clickerEle = document.getElementById("clicker");
var dropperEle = document.getElementById("droppedClicks");
var deDs = dropperEle.dataset; // deDs = droperEle DataSet
var dropSkips = 0;
function whenClick(){
    if (dropSkips > 10) { // if the user clicked fast enough
        mute(clickerEle, "click", whenClick, theOptions);
        dropperEle.textContent = "You won with " + dropSkips + 
            " clicks per second! The button no longer changes color";
    }
    dropSkips = 0;
    deDs ? delete deDs.numdrops : dropperEle.removeAttribute("data-numdrops");
    clickerEle.setAttribute("style", "background:#"+randHex()+randHex()+randHex());
}
var theOptions = {
    ALTERNATE: function(){
        // whenever the main function is skipped:
        deDs.numdrops = dropSkips += 1;
    },
    INTERVAL: 2000,
    passive: true
};
listen(clickerEle, "click", whenClick, theOptions);
whenClick(); // to start changing the color
document.body.addEventListener("contextmenu", function(x){x.preventDefault()});
})();
#droppedClicks[data-numdrops]::before {
    content: "Dropped " attr(data-numdrops) " clicks";
    color: green;
}
Click the button below as fast as you can! You win when you are able to click the button more than ten times in a single second ().<br />
<br />
<button id="clicker"><h3>Click me</h3></button>
<div id="droppedClicks"></div>