Führen Sie eine Entprellung in React.js durch


496

Wie führt man ein Debounce in React.js durch?

Ich möchte das handleOnChange entprellen.

Ich habe es versucht, debounce(this.handleOnChange, 200)aber es funktioniert nicht.

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

Ich habe das gleiche Problem mit Ihnen getroffen, hervorragende Antworten unten!, aber ich denke, Sie haben einen falschen Weg eingeschlagen debounce. hier, wann onChange={debounce(this.handleOnChange, 200)}/>, wird es debounce functionjedes Mal aufgerufen . Tatsächlich müssen wir jedoch die Funktion aufrufen, die die Entprellungsfunktion zurückgegeben hat.
Pingfengafei

Antworten:


835

2019: versuchen Sie Hooks + Versprechen Entprellen

Dies ist die aktuellste Version, wie ich dieses Problem lösen würde. Ich würde ... benutzen:

Dies ist eine anfängliche Verkabelung, aber Sie erstellen selbst primitive Blöcke, und Sie können Ihren eigenen benutzerdefinierten Hook erstellen, sodass Sie dies nur einmal tun müssen.

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

Und dann können Sie Ihren Haken benutzen:

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

Dieses Beispiel wird hier ausgeführt. Weitere Informationen finden Sie in der Dokumentation zum React-Async-Hook .


2018: Versuchen Sie, das Versprechen zu entprellen

Wir möchten häufig API-Aufrufe entprellen, um zu vermeiden, dass das Backend mit nutzlosen Anforderungen überflutet wird.

Im Jahr 2018 fühlt sich die Arbeit mit Rückrufen (Lodash / Underscore) für mich schlecht und fehleranfällig an. Aufgrund von API-Aufrufen, die in einer beliebigen Reihenfolge gelöst werden, können Probleme mit Boilerplate und Parallelität leicht auftreten.

Ich habe eine kleine Bibliothek mit dem Gedanken an React erstellt, um Ihre Probleme zu lösen: Awesome-Debounce-Versprechen .

Dies sollte nicht komplizierter sein:

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

Die entprellte Funktion stellt sicher, dass:

  • API-Aufrufe werden entprellt
  • Die entprellte Funktion gibt immer ein Versprechen zurück
  • Nur das vom letzten Anruf zurückgegebene Versprechen wird gelöst
  • this.setState({ result });Pro API-Aufruf wird eine einzige ausgeführt

Möglicherweise können Sie einen weiteren Trick hinzufügen, wenn die Bereitstellung Ihrer Komponente aufgehoben wird:

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

Beachten Sie, dass Observables (RxJS) auch gut zum Entprellen von Eingaben geeignet sind, aber eine leistungsfähigere Abstraktion ist, die möglicherweise schwieriger zu erlernen / richtig zu verwenden ist.


<2017: Möchten Sie das Callback-Debouncing weiterhin verwenden?

Der wichtige Teil hierbei ist das Erstellen einer einzelnen entprellen (oder gedrosselten) Funktion pro Komponenteninstanz . Sie möchten die Entprellungs- (oder Drossel-) Funktion nicht jedes Mal neu erstellen, und Sie möchten nicht, dass mehrere Instanzen dieselbe entprellende Funktion gemeinsam nutzen.

Ich definiere in dieser Antwort keine Entprellungsfunktion, da sie nicht wirklich relevant ist, aber diese Antwort funktioniert perfekt mit _.debounceUnterstrichen oder Lodash sowie mit allen vom Benutzer bereitgestellten Entprellungsfunktionen.


GUTE IDEE:

Da entprellte Funktionen statusbehaftet sind, müssen wir eine entprellte Funktion pro Komponenteninstanz erstellen .

ES6 (Klasseneigenschaft) : empfohlen

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

ES6 (Klassenkonstruktor)

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

Siehe JsFiddle : 3 Instanzen erzeugen 1 Protokolleintrag pro Instanz (das sind 3 global).


Keine gute Idee:

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

Dies funktioniert nicht, da bei der Klassenbeschreibung die Objekterstellung thisnicht das selbst erstellte Objekt ist. this.methodgibt nicht zurück, was Sie erwarten, da der thisKontext nicht das Objekt selbst ist (das tatsächlich noch nicht wirklich existiert, übrigens, da es gerade erstellt wird).


Keine gute Idee:

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

Dieses Mal erstellen Sie effektiv eine entprellte Funktion, die Ihre aufruft this.method. Das Problem ist, dass Sie es bei jedem debouncedMethodAnruf neu erstellen, sodass die neu erstellte Entprellungsfunktion nichts über frühere Anrufe weiß! Sie müssen dieselbe entprellte Funktion im Laufe der Zeit wiederverwenden, da sonst sonst keine Entprellung erfolgt.


Keine gute Idee:

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

Das ist hier etwas knifflig.

Alle gemounteten Instanzen der Klasse haben dieselbe entprellte Funktion, und meistens ist dies nicht das, was Sie wollen!. Siehe JsFiddle : 3 Instanzen produzieren weltweit nur 1 Protokolleintrag.

Sie müssen für jede Komponenteninstanz eine entprellte Funktion erstellen und nicht eine einzelne entprellte Funktion auf Klassenebene, die von jeder Komponenteninstanz gemeinsam genutzt wird.


Kümmere dich um das Event-Pooling von React

Dies hängt damit zusammen, dass wir DOM-Ereignisse häufig entprellen oder drosseln möchten.

In React werden die Ereignisobjekte (dh SyntheticEvent), die Sie in Rückrufen erhalten, zusammengefasst (dies ist jetzt dokumentiert ). Dies bedeutet, dass nach dem Aufruf des Ereignisrückrufs das empfangene SyntheticEvent mit leeren Attributen wieder in den Pool gestellt wird, um den GC-Druck zu verringern.

Wenn Sie also SyntheticEventasynchron auf Eigenschaften des ursprünglichen Rückrufs zugreifen (wie dies beim Drosseln / Entprellen der Fall sein kann), werden die Eigenschaften, auf die Sie zugreifen, möglicherweise gelöscht. Wenn Sie möchten, dass das Ereignis nie wieder in den Pool aufgenommen wird, können Sie die persist()Methode verwenden.

Ohne Persistenz (Standardverhalten: gepooltes Ereignis)

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

Der zweite (asynchron) wird gedruckt, hasNativeEvent=falseda die Ereigniseigenschaften bereinigt wurden.

Mit bestehen

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

Der zweite (asynchron) wird gedruckt, hasNativeEvent=trueda persistSie vermeiden können, dass das Ereignis wieder in den Pool verschoben wird .

Sie können diese beiden Verhaltensweisen hier testen : JsFiddle

In Julens Antwort finden Sie ein Beispiel für die Verwendung persist()mit einer Gas- / Entprellfunktion.


3
Hervorragende Antwort: Dies ist ideal, um den
Status

8
Beachten Sie, dass Sie in ES6, anstatt Ihre Methode im Konstruktor zu definieren (fühlt sich komisch an), dies handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)auf der obersten Ebene Ihrer Klasse tun können . Sie legen immer noch effektiv ein Instanzmitglied fest, aber es ähnelt eher einer normalen Methodendefinition. Keine Notwendigkeit für eine, constructorwenn Sie noch keine definiert haben. Ich nehme an, es ist meistens eine Stilpräferenz.
thom_nic

24
Vergessen Sie nicht, die entprellte Methode abzubrechen in componentWillUnmount: this.method.cancel()- Andernfalls möchte sie möglicherweise den Status für eine nicht gemountete Komponente festlegen.
Elado

4
@ JonasKello Sie können innerhalb einer zustandslosen Komponente nicht entprellen, da die entprellte Funktion tatsächlich statusbehaftet ist. Sie benötigen eine statusbehaftete Komponente, um diese entprellte Funktion zu speichern. Sie können jedoch bei Bedarf eine zustandslose Komponente mit einer bereits entprellten Funktion aufrufen.
Sebastien Lorber

2
Warum enthält jede Antwort _.debounce, anstatt die Funktion zu schreiben? Benötigt es die gesamte Bibliothek für diese Funktion?
Chifliiiii

217

Unkontrollierte Komponenten

Sie können die event.persist()Methode verwenden .

Es folgt ein Beispiel mit Unterstrichen _.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} />
    );
  }

});

Bearbeiten: Siehe diese JSFiddle


Kontrollierte Komponenten

Update: Das obige Beispiel zeigt eine unkontrollierte Komponente . Ich benutze die ganze Zeit kontrollierte Elemente, daher hier ein weiteres Beispiel für das oben event.persist()Genannte , aber ohne den "Trick" zu verwenden.

Eine JSFiddle ist ebenfalls verfügbar . Beispiel ohne Unterstrich

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

Bearbeiten: Aktualisierte Beispiele und JSFiddles to React 0.12

Bearbeiten: Aktualisierte Beispiele, um das von Sebastien Lorber aufgeworfene Problem zu beheben

Bearbeiten: aktualisiert mit jsfiddle, das keinen Unterstrich verwendet und einfaches Javascript-Debounce verwendet.


Dies funktioniert nicht für Eingaben. Das Ereignisziel in der entprellten Funktion hat keinen Wert mehr ... daher bleibt die Eingabe leer.
Etai

1
Etwas komplex das. Bei Requisiten muss man etwas vorsichtig sein. Wenn Sie festlegen <input value={this.props.someprop}..., wird es nicht richtig gerendert, da das Update beim Tastendruck erst nach dem Entprellen wieder in die Komponente zurückkehrt. Es ist in Ordnung, das wegzulassen, value=wenn Sie froh sind, dass dies nicht verwaltet wird, aber wenn Sie den Wert vorab ausfüllen und / oder an eine andere Stelle binden möchten, funktioniert dies offensichtlich nicht.
Alastair Maw

1
@AlastairMaw die Frage hatte eine unkontrollierte Komponente, deshalb hat die Antwort es auch. Ich habe unten eine alternative Version für gesteuerte Komponenten mit einem vorab ausgefüllten Wert hinzugefügt.
Julen

2
Dies ist sehr gefährlich, wenn Sie die Komponente mehrmals im DOM mounten
Sebastien Lorber

4
Obwohl dies eine großartige Antwort ist, empfehle ich die Verwendung nicht, persistinsbesondere wenn es viele Ereignisse gibt, wie z mousemove. Ich habe gesehen, dass Code auf diese Weise völlig nicht mehr reagiert. Es ist viel effizienter, die erforderlichen Daten aus dem nativen Ereignis im Ereignisaufruf zu extrahieren und dann die entprellte / gedrosselte Funktion nur mit den Daten aufzurufen, NICHT mit dem Ereignis selbst. Keine Notwendigkeit, das Ereignis auf diese Weise
fortzusetzen

31

2019: Verwenden Sie den Reaktionshaken 'useCallback'

Nachdem ich viele verschiedene Ansätze ausprobiert hatte, stellte ich fest useCallback, dass die Verwendung am einfachsten und effizientesten ist, um das Problem der Verwendung mehrerer Anrufe debounceinnerhalb eines onChangeEreignisses zu lösen .

Gemäß der Dokumentation Hooks API ,

useCallback gibt eine gespeicherte Version des Rückrufs zurück, die sich nur ändert, wenn sich eine der Abhängigkeiten geändert hat.

Durch Übergeben eines leeren Arrays als Abhängigkeit wird sichergestellt, dass der Rückruf nur einmal aufgerufen wird. Hier ist eine einfache Implementierung:

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

Hoffe das hilft!


3
Hervorragende Lösung, wenn Sie Haken verwenden. Du hast mir noch viele Stunden Frust erspart. Vielen Dank!
Carl Edwards

Könnten Sie bitte erklären, warum die Mehrfachanrufe überhaupt stattfinden? Hat debounce()nicht die betrachten onChange()Rückruf die gleiche Callback - Methode zu sein?
El Anonimo

Ich habe diese Lösung geändert, damit sie in meiner App funktioniert. Zuerst musste ich die Zeile const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);innerhalb des Hauptteils der Funktionskomponente verschieben, oder React gibt eine Fehlermeldung über die Verwendung von Hooks außerhalb der Funktionskomponente aus. Dann im onChangeEvent-Handler : <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.
El Anonimo

Hier ist, wie ich diese Lösung verwendet habe, um den Benutzer eine Eingabe eingeben zu lassen und dann einen entprellten API-Aufruf mit dem Eingabewert zu senden, sobald er mit der Eingabe fertig ist. stackoverflow.com/questions/59358092/… .
El Anonimo

14

Ich fand diesen Beitrag von Justin Tulk sehr hilfreich. Nach ein paar Versuchen, wie man es bei React / Redux als offizieller empfinden würde, zeigt sich, dass es aufgrund des Pools synthetischer Ereignisse von React fehlschlägt . Seine Lösung verwendet dann einen internen Status, um den in der Eingabe geänderten / eingegebenen Wert zu verfolgen setState, und ruft direkt danach eine gedrosselte / entprellende Redux-Aktion auf, die einige Ergebnisse in Echtzeit anzeigt.

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

Wenn Sie vom Ereignisobjekt nur das DOM-Eingabeelement abrufen müssen, ist die Lösung viel einfacher - verwenden Sie einfach ref. Beachten Sie, dass hierfür ein Unterstrich erforderlich ist :

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 ist was ich will! Vielen Dank mach :)
Tazo leladze

14

Nachdem ich eine Weile mit den Texteingaben zu kämpfen hatte und selbst keine perfekte Lösung gefunden hatte, fand ich dies auf npm: React-Debounce-Input .

Hier ist ein einfaches Beispiel:

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

Die DebounceInput-Komponente akzeptiert alle Requisiten, die Sie einem normalen Eingabeelement zuweisen können. Probieren Sie es auf Codepen aus

Ich hoffe, es hilft auch jemand anderem und spart ihm Zeit.


Nachdem ich viele der hier aufgeführten Lösungen ausprobiert hatte, war dies definitiv die einfachste.
Vadorequest

9

Mit müssen debounceSie das ursprüngliche synthetische Ereignis mit behalten event.persist(). Hier ist ein Arbeitsbeispiel getestet mit 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;

Mit der Funktionskomponente können Sie dies tun -

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

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


1
funktioniert großartig beste Implementierung, die ich bisher gefunden habe
Vincent Tang

8

Wenn Sie Redux verwenden, können Sie dies mit Middleware auf sehr elegante Weise tun. Sie können eine DebounceMiddleware wie folgt definieren :

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

Anschließend können Sie Aktionserstellern Entprellungen hinzufügen, z.

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

Es gibt tatsächlich bereits Middleware, mit der Sie npm aussteigen können, um dies für Sie zu tun.


Ich denke, diese Middleware muss die erste sein, die in einer applyMiddleware(...)Kette ausgeführt wird , wenn wir viele haben
Youssef

Das Timeout wird nicht initialisiert und das erste clearTimeout behandelt undefined für einen Parameter. Nicht gut.
Jason Rice

7

Verwenden von ES6 CLASS und React 15.xx & lodash.debounce Ich verwende hier die Refs von React, da Ereignisverluste dies intern binden.

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

Viele gute Infos hier schon, aber um es kurz zu machen. Das funktioniert bei mir ...

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

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

Das funktioniert bei mir nicht. Der Status wird nicht aktualisiert. Wenn ich _debounce Wrapper entferne , funktioniert es. Ich liebe diese Idee!
Mote Zart

Ich müsste Ihren Code sehen, um hier viel zu bieten, aber ich vermute, dass noch etwas los ist ... hoffentlich wird diese viel gründlichere Antwort etwas Licht ins Dunkel bringen. stackoverflow.com/questions/23123138/…
Chad Steele

6

Sie können die Lodash-Debounce- Methode https://lodash.com/docs/4.17.5#debounce verwenden. Es ist einfach und effektiv.

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

Sie können die Entprellungsmethode auch mit der folgenden Methode abbrechen.

this.debounceHandleUpdate.cancel();

Hoffe es hilft dir. Prost!!


5

Zu Ihrer Information

Hier ist eine weitere PoC-Implementierung:

  • ohne Bibliotheken (zB lodash) zum Entprellen
  • Verwenden der React Hooks-API

Ich hoffe, es hilft :)

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

Es gibt ein use-debouncePaket, das Sie mit ReactJS-Hooks verwenden können.

Aus der README-Datei des Pakets:

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

Wie Sie dem obigen Beispiel entnehmen können, wird die Variable valuenur einmal pro Sekunde (1000 Millisekunden) aktualisiert .


3

Nur eine weitere Variante mit neueren Reaktionen und Lodash.

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

Eine schöne und saubere Lösung, die keine externen Abhängigkeiten erfordert:

Entprellen mit React Hooks

Es verwendet ein benutzerdefiniertes Plus sowie die useEffect React-Hooks und die setTimeout/ clearTimeout-Methode.


3

Hast du versucht?

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

Anstatt den handleOnChange in ein debounce () zu verpacken, sollten Sie den Ajax-Aufruf in die Rückruffunktion in das Entprellen einbinden und dabei das Ereignisobjekt nicht zerstören. Also so etwas wie das:

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

4
Da das Ereignisobjekt nicht unveränderlich ist und von ReactJS zerstört wird, schlägt der Code selbst dann fehl, wenn Sie eine Abschlusserfassung einschließen und erreichen.
Henrik

2

Hier ist ein Beispiel, das ich mir ausgedacht habe und das eine andere Klasse mit einem Debouncer umschließt. Dies eignet sich gut, um zu einer Dekorateur- / Funktion höherer Ordnung gemacht zu werden:

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

Es gibt jetzt eine andere Lösung für React and React Native Ende 2019 :

React-Debounce-Komponente

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

Es ist eine Komponente, einfach zu bedienen, winzig und breit unterstützt

Beispiel:

Geben Sie hier die Bildbeschreibung ein

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;

* Ich bin der Schöpfer dieser Komponente


1

Ich habe nach einer Lösung für das gleiche Problem gesucht und bin auf diesen und einige andere Threads gestoßen, aber sie hatten das gleiche Problem: Wenn Sie versuchen, eine handleOnChangeFunktion auszuführen, und Sie den Wert eines Ereignisziels benötigen, erhalten Sie cannot read property value of nulloder einige ein solcher Fehler. In meinem Fall musste ich auch den Kontext thisinnerhalb der entprellten Funktion beibehalten, da ich eine fließfähige Aktion ausführe. Hier ist meine Lösung, sie funktioniert gut für meinen Anwendungsfall, also lasse ich sie hier, falls jemand auf diesen Thread stößt:

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

zum throttle oder debounceam besten erstellen Sie einen Funktionsersteller, damit Sie ihn überall verwenden können, zum Beispiel:

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

und in deinem render Methode können Sie tun:

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

das updateUserProfileField Methode erstellt bei jedem Aufruf eine separate Funktion.

Hinweis: Versuchen Sie nicht, den Handler direkt zurückzugeben. Dies funktioniert beispielsweise nicht:

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

der Grund, warum dies nicht funktioniert, weil dies jedes Mal eine neue Drosselfunktion erzeugt, wenn das Ereignis aufgerufen wird, anstatt dieselbe Drosselfunktion zu verwenden, so dass die Drossel im Grunde genommen unbrauchbar ist;)

Auch wenn Sie verwenden debounceoder throttlenicht benötigen setTimeoutoder clearTimeout, verwenden wir sie tatsächlich aus diesem Grund: P.


1

Hier ist ein Ausschnitt aus dem Ansatz von @ Abra, der in eine Funktionskomponente eingeschlossen ist (wir verwenden Fabric für die Benutzeroberfläche, ersetzen Sie ihn einfach durch eine einfache Schaltfläche).

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

Meine Lösung basiert auf Hooks (geschrieben in Typescript).

Ich habe 2 Haupthaken useDebouncedValueunduseDebouncedCallback

Zuerst - useDebouncedValue

Angenommen, wir haben ein Suchfeld, aber wir möchten den Server nach Suchergebnissen fragen, nachdem der Benutzer für 0,5 Sekunden aufgehört hat zu tippen

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

Implementierung

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

Zweite useDebouncedCallback

Es wird lediglich eine "entprellte" Funktion im Bereich Ihrer Komponente erstellt.

Angenommen, wir haben eine Komponente mit einer Schaltfläche, die 500 ms nach dem Klicken darauf eine Warnung anzeigt.

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

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

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

Implementierung (Hinweis Ich verwende lodash / debounce als Helfer)

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

Hier ist ein funktionierendes TypeScript-Beispiel für diejenigen, die TS verwenden und asyncFunktionen entprellen möchten .

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

ein bisschen spät hier, aber das sollte helfen. Erstellen Sie diese Klasse (sie ist in Typoskript geschrieben, aber einfach in Javascript zu konvertieren).

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

und zu verwenden

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

0

Sie können tlence tlence verwenden

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

Die Julen-Lösung ist schwer zu lesen. Hier ist ein klarerer und präziser Reaktionscode für alle, die ihn aufgrund des Titels und nicht aufgrund der winzigen Details der Frage gestolpert haben.

tl; dr version : Wenn Sie ein Update an Beobachter senden würden, rufen Sie stattdessen eine Zeitplanmethode auf, die die Beobachter tatsächlich benachrichtigt (oder Ajax usw. ausführt).

Vervollständige jsfiddle mit der Beispielkomponente 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
Wird dadurch der Status / das Timing des Debounces nicht für alle Instanzen von InputField global, da es mit der Klassendefinition erstellt wird? Vielleicht ist es das, was Sie wollen, aber es ist trotzdem erwähnenswert.
Robbles

1
gefährlich, wenn mehrmals
Sebastien Lorber

2
Dies ist eine schlechte Lösung, da Probleme mit der doppelten Bereitstellung auftreten. Sie machen Ihre Funktion zum Planen. Ändern Sie einen Singleton, und das ist keine gute Idee. -1
Henrik

0

Vermeiden event.persist()Sie die Verwendung - Sie möchten, dass React das synthetische Ereignis recycelt. Ich denke, der sauberste Weg, ob Sie Klassen oder Hooks verwenden, besteht darin, den Rückruf in zwei Teile aufzuteilen:

  1. Der Rückruf ohne Entprellung
  2. Ruft eine entprellte Funktion mit nur den Teilen des Ereignisses auf, die Sie benötigen (damit das synthetische Ereignis recycelt werden kann).

Klassen

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

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

<div onMouseOver={this.onMouseOver} />

Funktionen

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

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

<div onMouseOver={this.onMouseOver} />

Beachten Sie, dass Sie, wenn Ihre handleMouseOverFunktion den Status innerhalb der Komponente verwendet, diese useMemoanstelle von useRefund als Abhängigkeiten übergeben sollten, da Sie sonst mit veralteten Daten arbeiten (gilt natürlich nicht für Klassen).


0

Erweitern Sie den 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;

Haken verwenden

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

Habe dieses Problem heute getroffen. Es wurde mit setTimeoutund gelöst clearTimeout.

Ich werde ein Beispiel geben, das Sie anpassen könnten:

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

Sie können es auch in einer eigenen Funktionskomponente umgestalten:

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

Und benutze es wie:

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.