it-swarm.com.de

Debounce in React.js durchführen

Wie führen Sie eine Entprellung in React.js durch?

Ich möchte das handleOnChange entprellen.

Ich habe es mit debounce(this.handleOnChange, 200) versucht, 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
    }
});
355
Chetan Ankola

2018: Versprich das Entprellen

Wir möchten häufig API-Aufrufe entprellen, um das Backend nicht mit nutzlosen Anforderungen zu überfluten.

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

Ich habe eine kleine Bibliothek mit React erstellt, um Ihre Schmerzen zu lösen: awesome-debounce-promise .

Das sollte nicht komplizierter sein als das:

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 zurückgegebene Versprechen des letzten Anrufs wird aufgelöst
  • pro API-Aufruf wird eine einzelne this.setState({ result }); ausgeführt

Eventuell können Sie einen weiteren Trick hinzufügen, wenn Ihre Komponente nicht gemountet wird:

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

Beachten Sie, dass Observables (RxJS) sich auch hervorragend für die Entprellung von Eingaben eignet. Es handelt sich jedoch um eine leistungsfähigere Abstraktion, die möglicherweise schwieriger zu erlernen/zu verwenden ist.


Möchten Sie noch Callback-Entprellung verwenden?

Der wichtige Teil hier ist um eine einzelne entprellte (oder gedrosselte) Funktion pro Komponenteninstanz zu erstellen. Sie möchten die Entprellungs- (oder Drossel-) Funktion nicht jedes Mal neu erstellen, und Sie möchten auch nicht, dass mehrere Instanzen dieselbe entprellte Funktion gemeinsam nutzen.

Ich definiere in dieser Antwort keine Entprellungsfunktion, da sie nicht wirklich relevant ist, aber diese Antwort wird perfekt mit _.debounce von Unterstrich oder Lodash sowie jeder vom Benutzer bereitgestellten Entprellungsfunktion funktionieren.


GUTE IDEE:

Da entprellte Funktionen stateful sind, müssen Sie 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,1000);
    }
    method() { ... }
}

ES5

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

Siehe JsFiddle : 3-Instanzen erzeugen einen Protokolleintrag pro Instanz (das macht 3 global).


Keine gute Idee:

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

Dies funktioniert nicht, da this während der Erstellung von Klassenbeschreibungsobjekten nicht das Objekt selbst ist. this.method gibt nicht das zurück, was Sie erwarten, da der this-Kontext nicht das Objekt selbst ist (das eigentlich noch nicht wirklich vorhanden ist, da es gerade erst 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 Ihren this.method aufruft. Das Problem ist, dass Sie es bei jedem debouncedMethod-Aufruf neu erstellen, sodass die neu erstellte Debounce-Funktion nichts über frühere Aufrufe weiß! Sie müssen dieselbe entprellte Funktion im Laufe der Zeit wiederverwenden, andernfalls tritt keine Entprellung auf.


Keine gute Idee:

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

Das ist hier etwas knifflig. 

Alle gemounteten Instanzen der Klasse verwenden dieselbe entprellte Funktion, und meistens ist es nicht das, was Sie wollen! Siehe JsFiddle : 3-Instanzen erzeugen weltweit nur einen Protokolleintrag.

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


Achten Sie auf 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 (d. H. SyntheticEvent), die Sie in Rückrufen empfangen, in einem Pool zusammengefasst (dies ist jetzt documented ). Das bedeutet, dass nach dem Aufruf des Ereignisrückrufs das von Ihnen erhaltene SyntheticEvent mit leeren Attributen in den Pool zurückgesetzt wird, um den GC-Druck zu reduzieren.

Wenn Sie also auf SyntheticEvent-Eigenschaften asynchron auf den ursprünglichen Callback zugreifen (wie dies bei Drosselung/Entprellung 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 verschoben wird, können Sie die persist()-Methode verwenden.

Ohne persistieren (Standardverhalten: gepooltes Ereignis)

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

Der zweite (asynchron) druckt hasNativeEvent=false, da die Ereigniseigenschaften bereinigt wurden.

Mit beharrlich bleiben

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

Die zweite (async) druckt hasNativeEvent=true, da Sie mit persist vermeiden können, das Ereignis zurück in den Pool zu setzen.

Sie können diese beiden Verhaltensweisen hier testen: JsFiddle

Lesen Sie Julens Antwort für ein Beispiel für die Verwendung von persist() mit einer Drossel-/Entprellungsfunktion.

702

Unkontrollierte Komponenten

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

Ein Beispiel folgt mit der Unterfunktion _.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} />
    );
  }

});

Edit: Siehe this JSFiddle


Kontrollierte Komponenten

Update: Das obige Beispiel zeigt eine unkontrollierte Komponente . Ich verwende die ganze Zeit kontrollierte Elemente, also hier ein weiteres Beispiel für das oben Genannte, jedoch ohne event.persist() "trickery".

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

Edit: aktualisierte Beispiele und JSFiddles to React 0.12

Bearbeiten: Aktualisierte Beispiele für das von Sebastien Lorber aufgeworfene Problem

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

192
julen

Wenn Sie für das Ereignisobjekt lediglich das DOM-Eingabeelement benötigen, ist die Lösung viel einfacher - verwenden Sie einfach ref. Beachten Sie, dass dies Underscore erfordert:

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} />
    }
}
11
Yura

Ich fand diesen Beitrag von Justin Tulk sehr hilfreich. Nach ein paar Versuchen, wie man es als die offiziellere Art mit rea/redux empfinden würde, zeigt es, dass es aufgrund von Reacts synthetischem Ereignis-Pooling versagt. Seine Lösung verwendet dann einen internen Status, um den in der Eingabe geänderten/eingegebenen Wert zu verfolgen, mit einem Rückruf direkt nach setState, der eine gedrosselte/entprellte Redux-Aktion aufruft, 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}
        />
    )
  }
}
8
lxm7

Nachdem ich mich einige Zeit mit den Texteingaben herumgekämpft hatte und selbst keine perfekte Lösung gefunden hatte, fand ich diese auf npm https://www.npmjs.com/package/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 zuordnen können. Probiere es auf codepen aus

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

6
Hooman Askari

Verwenden von ES6 CLASS und React 15.x.x & lodash.debounceIm Verwenden von Reacts refs hier, da Ereignisverluste das 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/[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>

6
STEEL

Hier gibt es schon viele gute Infos, aber um es kurz zu machen. Das funktioniert für mich ...

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

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

Wenn Sie redux verwenden, können Sie dies mit Middleware auf sehr elegante Weise tun. Sie können eine Debounce-Middleware definieren als:

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

Sie können den Aktionserstellern dann Entprellung hinzufügen, z.

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

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

5
Matt

Sie können Lodash debounce 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 Debounce-Methode auch mit der folgenden Methode abbrechen.

this.debounceHandleUpdate.cancel();

Ich hoffe es hilft dir. Prost!!

4

Hier ist ein Beispiel, das mir einfiel, das eine andere Klasse mit einem Debouncer abschließt. Dies ist gut geeignet, um eine Dekorateur/Funktion höherer Ordnung 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
mlucool

Anstatt das handleOnChange in ein debounce () zu packen, kann man den Ajax-Aufruf nicht innerhalb der Callback-Funktion in das Debounce einschließen, wodurch das Ereignisobjekt nicht zerstört wird. Also so etwas wie:

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

Ich habe nach einer Lösung für das gleiche Problem gesucht und bin auf diesen Thread sowie auf einige andere gestoßen, aber sie hatten das gleiche Problem: Wenn Sie versuchen, eine handleOnChange-Funktion auszuführen, und Sie den Wert eines Ereignisziels benötigen, erhalten Sie cannot read property value of null oder einen solchen Fehler. In meinem Fall musste ich auch den Kontext von this in der entprägten Funktion beibehalten, da ich eine fließfähige Aktion ausführte. Hier ist meine Lösung, es funktioniert gut für meinen Anwendungsfall, also lasse ich es 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
Edward

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

Entprellen mit React Hooks

Es verwendet eine benutzerdefinierte Methode sowie die Hooks useEffect React und die Methode setTimeout/clearTimeout.

1
Bruno Silvano

Mit debounce müssen Sie das ursprüngliche synthetische Ereignis mit event.persist() beibehalten. Hier ist ein mit React 16+ getestetes Arbeitsbeispiel.

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;

Reference Gist - https://Gist.github.com/elijahmanor/08fc6c8468c994c844213e4a4344a709

1
Mohan Dere

für throttle oder debounce ist es am besten, wenn Sie einen Funktionsersteller erstellen, um ihn überall verwenden zu können, zum Beispiel:

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

und in Ihrer render-Methode können Sie Folgendes tun:

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

die updateUserProfileField-Methode erstellt bei jedem Aufruf eine separate Funktion.

Hinweis Versuchen Sie nicht, den Handler direkt zurückzugeben, zum Beispiel wird dies nicht funktionieren:

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

der Grund, warum dies nicht funktionieren wird, weil dies bei jedem Aufruf des Ereignisses eine neue Drosselfunktion generiert, anstatt dieselbe Drosselfunktion zu verwenden, so dass die Drosselung im Grunde nutzlos ist;)

Auch wenn Sie debounce oder throttle verwenden, brauchen Sie setTimeout oder clearTimeout nicht. Deshalb verwenden wir sie: P

1

Zu Ihrer Information

Hier ist eine weitere PoC-Implementierung:

  • ohne Bibliotheken (z. B. lodash) zur Entprellung
  • mit der React Hooks API

Ich hoffe, es hilft :)

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

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

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

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

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

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

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

Hier ist ein funktionierendes TypeScript-Beispiel für diejenigen, die TS verwenden und async-Funktionen 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
Andrei

ein bisschen spät hier, aber das sollte helfen. erstelle diese Klasse (sie ist in TypeScript 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
anaval

sie können tlence verwenden https://www.npmjs.com/package/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

Nur eine andere Variante mit kürzlich reagierten 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)} />)
  }
}
0
puchu

Die Lösung von Julen ist schwer zu lesen, hier ist ein klarer und auf den Punkt genau reagierender Code für alle, die ihn aufgrund des Titels gestolpert haben und nicht die winzigen Details der Frage.

tl; dr version: Wenn Sie ein Update für Beobachter durchführen würden, senden Sie stattdessen eine Zeitplanmethode, und dies wird die Beobachter tatsächlich benachrichtigen (oder Ajax ausführen usw.).

Komplette jsfiddle mit Beispielkomponente http://jsfiddle.net/7L655p5L/4/

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

});
0
srcspider