Przeprowadź debounce w React.js


496

Jak przeprowadzasz debounce w React.js?

Chcę ogłosić uchwytOnChange.

Próbowałem z, debounce(this.handleOnChange, 200)ale to nie działa.

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

Spotkałem z tobą ten sam problem, poniżej wspaniałe odpowiedzi!, ale myślę, że użyłeś niewłaściwego sposobu debounce. tutaj, kiedy onChange={debounce(this.handleOnChange, 200)}/>będzie przywoływać za debounce functionkażdym razem. ale w rzeczywistości potrzebujemy wywołać funkcję, którą zwróciła funkcja debounce.
pingfengafei

Odpowiedzi:


834

2019: wypróbuj haki + zapowiedź obietnicy

To jest najbardziej aktualna wersja tego, jak rozwiązałbym ten problem. Użyłbym:

To jest trochę wstępne okablowanie, ale sam tworzysz prymitywne bloki i możesz zrobić swój własny hak, abyś musiał to zrobić tylko raz.

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

A potem możesz użyć haka:

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

Ten przykład znajduje się tutaj . Aby uzyskać więcej informacji, przeczytaj dokumentację przechwytującą reakcję asynchroniczną .


2018: spróbuj ogłosić obietnicę

Często chcemy ogłaszać wywołania interfejsu API, aby uniknąć zalania zaplecza niepotrzebnymi żądaniami.

W 2018 r. Praca z wywołaniami zwrotnymi (Lodash / Underscore) jest dla mnie zła i podatna na błędy. Łatwo jest napotkać problemy z podstawowymi funkcjami i współbieżnością, ponieważ wywołania API są rozwiązywane w dowolnej kolejności.

Stworzyłem małą bibliotekę z myślą o React, aby rozwiązać wasze problemy: niesamowita obietnica wypowiedzenia .

Nie powinno to być bardziej skomplikowane:

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

Zadeklarowana funkcja zapewnia, że:

  • Połączenia API będą ogłaszane
  • funkcja zadeklarowana zawsze zwraca obietnicę
  • rozwiązana zostanie tylko obietnica zwrócona z ostatniego połączenia
  • this.setState({ result });wydarzy się pojedynczy na każde wywołanie API

W końcu możesz dodać kolejną sztuczkę, jeśli twój komponent odmontuje:

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

Zauważ, że Observables (RxJS) może również doskonale nadawać się do ogłaszania danych wejściowych, ale jest to bardziej wydajna abstrakcja, która może być trudniejsza do nauczenia się / używania poprawnie.


<2017: Nadal chcesz korzystać z zapowiedzi wywołania zwrotnego?

Ważną częścią jest tutaj utworzenie jednej zadeklarowanej (lub ograniczonej) funkcji dla każdej instancji komponentu . Nie chcesz ponownie odtwarzać funkcji debounce (lub przepustnicy) za każdym razem i nie chcesz, aby wiele instancji współużytkowało tę samą funkcję debounce.

Nie definiuję funkcji ogłaszającej w tej odpowiedzi, ponieważ nie jest ona tak naprawdę istotna, ale ta odpowiedź będzie doskonale działać z _.debouncepodkreśleniem lub lodash, a także z dowolną funkcją ogłaszającą podaną przez użytkownika.


DOBRY POMYSŁ:

Ponieważ funkcje zapowiadane są stanowe, musimy utworzyć jedną funkcję zapowiedzianą na instancję składnika .

ES6 (właściwość klasy) : zalecane

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

ES6 (konstruktor klasy)

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

Zobacz JsFiddle : 3 instancje generują 1 pozycję dziennika na instancję (co daje 3 globalnie).


NIE jest to dobry pomysł:

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

To nie zadziała, ponieważ podczas tworzenia opisu klasy obiekt thisnie jest sam obiekt utworzony. this.methodnie zwraca tego, czego oczekujesz, ponieważ thiskontekst nie jest samym obiektem (który tak naprawdę jeszcze nie istnieje BTW, ponieważ właśnie jest tworzony).


NIE jest to dobry pomysł:

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

Tym razem efektywnie tworzysz zapowiadaną funkcję, która wywołuje Twoją this.method. Problem polega na tym, że odtwarzasz go przy każdym debouncedMethodwywołaniu, więc nowo utworzona funkcja usuwania nie wie nic o poprzednich połączeniach! Z czasem musisz ponownie użyć tej samej funkcji, która została ogłoszona, w przeciwnym razie ogłoszenie nie nastąpi.


NIE jest to dobry pomysł:

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

To trochę trudne.

Wszystkie zamontowane instancje klasy będą miały tę samą zapowiedzianą funkcję i najczęściej nie jest to to, czego chcesz! Zobacz JsFiddle : 3 instancje produkują tylko 1 pozycję dziennika na całym świecie.

Musisz utworzyć funkcję, która została zadeklarowana dla każdej instancji składnika , a nie pojedynczą, zadeklarowaną funkcję na poziomie klasy, wspólną dla każdej instancji składnika.


Zadbaj o łączenie zdarzeń React

Jest to powiązane, ponieważ często chcemy ogłosić lub zdławić zdarzenia DOM.

W React obiekty zdarzeń (tj. SyntheticEvent), Które otrzymujesz w wywołaniach zwrotnych, są pulowane (jest to teraz udokumentowane ). Oznacza to, że po wywołaniu wywołania zwrotnego zdarzenia otrzymane SyntheticEvent zostanie ponownie umieszczone w puli z pustymi atrybutami w celu zmniejszenia presji GC.

Jeśli więc uzyskujesz dostęp do SyntheticEventwłaściwości asynchronicznie do pierwotnego wywołania zwrotnego (jak w przypadku dławienia / usuwania błędów), właściwości, do których masz dostęp, mogą zostać usunięte. Jeśli chcesz, aby zdarzenie nigdy nie było ponownie umieszczane w puli, możesz użyć tej persist()metody.

Bez utrwalania (zachowanie domyślne: zdarzenie połączone)

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

Drugi (asynchroniczny) zostanie wydrukowany, hasNativeEvent=falseponieważ właściwości zdarzenia zostały wyczyszczone.

Z wytrwałością

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

Drugi (asynchroniczny) zostanie wydrukowany, hasNativeEvent=trueponieważ persistpozwala uniknąć ponownego umieszczenia zdarzenia w puli.

Możesz przetestować te 2 zachowania tutaj: JsFiddle

Przeczytaj odpowiedź Julen, aby zobaczyć przykład korzystania persist()z funkcji przepustnicy / odbicia.


3
Znakomita odpowiedź, jest świetny do ustawiania stanu pól formularza jako „interakcji” przez kilka sekund po tym, jak przestają pisać, a następnie możliwość anulowania po przesłaniu formularza lub na
OnBlur

8
Zauważ, że w ES6 zamiast definiować metodę wewnątrz konstruktora (wydaje się dziwne), możesz to zrobić handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)na najwyższym poziomie swojej klasy. Nadal skutecznie ustawiasz element instancji, ale wygląda to bardziej jak normalna definicja metody. Nie ma potrzeby, constructorjeśli jeszcze go nie masz. Przypuszczam, że jest to przede wszystkim styl.
thom_nic

24
Nie zapomnij anulować zadeklarowanej metody w componentWillUnmount: this.method.cancel()- w przeciwnym razie może chcieć ustawić parametr Stan na odmontowanym komponencie.
elado

4
@JonasKello nie można debaskować w komponencie bezstanowym, ponieważ zadeklarowana funkcja jest faktycznie stanowa. Potrzebny jest komponent stanowy, aby utrzymać tę zadeklarowaną funkcję, ale w razie potrzeby można wywołać komponent bezstanowy z funkcją już zadeklarowaną.
Sebastien Lorber,

2
Dlaczego cała odpowiedź zawiera _.debounce zamiast pisania funkcji? Potrzebuje całej biblioteki do tej funkcji?
chifliiiii

217

Niekontrolowane komponenty

Możesz użyć tej event.persist()metody .

Oto przykład użycia podkreślenia _.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} />
    );
  }

});

Edycja: zobacz ten JSFiddle


Kontrolowane komponenty

Aktualizacja: powyższy przykład pokazuje niekontrolowany komponent . Cały czas używam kontrolowanych elementów, więc oto kolejny przykład powyższego, ale bez użycia event.persist()„oszustwa”.

JSFiddle jest dostępny także. Przykład bez podkreślenia

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

Edycja: zaktualizowano przykłady i JSFiddles do React 0.12

Edycja: zaktualizowano przykłady w celu rozwiązania problemu podniesionego przez Sebastiena Lorbera

Edycja: zaktualizowano o jsfiddle, który nie używa podkreślenia i używa zwykłego debounce javascript.


To nie działa dla danych wejściowych. Cel zdarzenia w zadeklarowanej funkcji nie ma już wartości ... więc dane wejściowe pozostają puste.
Etai

1
To trochę skomplikowane. Musisz być trochę ostrożny przy rekwizytach. Jeśli ustawisz, <input value={this.props.someprop}...to nie będzie renderowane poprawnie, ponieważ aktualizacja po naciśnięciu klawisza nie wróci do komponentu, aż po debounce. Można pominąć opcję, value=jeśli jesteś zadowolony z tego, że nie można nią zarządzać, ale jeśli chcesz wstępnie wypełnić wartość i / lub powiązać ją gdzie indziej, to oczywiście nie działa.
Alastair Maw

1
@AlastairMaw pytanie miało niekontrolowany komponent, dlatego też odpowiedź na nie. Dodałem poniżej alternatywną wersję dla kontrolowanych komponentów z wstępnie wypełnioną wartością.
lipca

2
jest to bardzo niebezpieczne, jeśli zamontujesz wiele komponentów w DOM, patrz stackoverflow.com/questions/23123138/...
Sebastien Lorber

4
chociaż jest to świetna odpowiedź, nie polecam jej używać, persistzwłaszcza gdy może być wiele wydarzeń, na przykład mousemove. Zauważyłem, że kod całkowicie przestał reagować w ten sposób. O wiele bardziej wydajne jest wyodrębnienie potrzebnych danych ze zdarzenia natywnego w wywołaniu zdarzenia, a następnie wywołanie funkcji debunowanej / dławionej tylko danymi, a NIE samego zdarzenia. Nie trzeba tak trwać w tym wydarzeniu
Mr

31

2019: Użyj haka reagującego „useCallback”

Po wypróbowaniu wielu różnych podejść odkryłem, że używanie useCallbackjest najprostszym i najbardziej wydajnym w rozwiązywaniu problemu wielu połączeń debouncew ramach onChangezdarzenia.

Zgodnie z dokumentacją interfejsu API Hooks ,

useCallback zwraca zapamiętaną wersję wywołania zwrotnego, która zmienia się tylko w przypadku zmiany jednej z zależności.

Przekazanie pustej tablicy jako zależności powoduje, że wywołanie zwrotne jest wywoływane tylko raz. Oto prosta implementacja:

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

Mam nadzieję że to pomoże!


3
Doskonałe rozwiązanie, jeśli używasz haków. Zaoszczędziłeś mi wiele godzin frustracji. Dzięki!
Carl Edwards,

Czy mógłbyś wyjaśnić, dlaczego wiele połączeń ma miejsce w pierwszej kolejności? Czy debounce()nie uważa onChange()wywołania zwrotnego za tę samą metodę wywołania zwrotnego?
El Anonimo,

Zmodyfikowałem to rozwiązanie, aby działało w mojej aplikacji. Najpierw musiałem przesunąć linię const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);wewnątrz ciała komponentu funkcji lub React wyświetla komunikat o błędzie dotyczący użycia haka poza nim. Następnie w onChangeobsługi zdarzeń: <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.
El Anonimo,

Oto jak użyłem tego rozwiązania, aby umożliwić użytkownikowi wpisanie danych wejściowych, a następnie wysłanie wywołanego interfejsu API z wartością wejściową po zakończeniu pisania. stackoverflow.com/questions/59358092/... .
El Anonimo,

14

Uznałem ten post za Justina Tulka za bardzo pomocny. Po kilku próbach, w sposób, który można by uznać za bardziej oficjalny w przypadku reakcji / redux, pokazuje, że nie udaje się to z powodu syntetycznego łączenia zdarzeń React . Jego rozwiązanie wykorzystuje pewien stan wewnętrzny do śledzenia wartości zmienionej / wprowadzonej na wejściu, z oddzwanianiem zaraz po setStatektórym wywołuje dławioną / zadeklarowaną akcję redux, która pokazuje niektóre wyniki w czasie rzeczywistym.

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

Jeśli wszystko, czego potrzebujesz od obiektu zdarzenia, to uzyskanie elementu wejściowego DOM, rozwiązanie jest znacznie prostsze - po prostu użyj ref. Pamiętaj, że wymaga to podkreślenia :

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 jest tym, czego chcę! Dziękuję bardzo mach :)
Tazo leladze

14

Po chwili zmagania się z wprowadzaniem tekstu i samodzielnego znalezienia idealnego rozwiązania znalazłem to na npm: reakcja-debounce-input .

Oto prosty przykład:

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

Składnik DebounceInput akceptuje wszystkie rekwizyty, które można przypisać do normalnego elementu wejściowego. Wypróbuj to na codepen

Mam nadzieję, że pomaga to również komuś innemu i oszczędza mu trochę czasu.


Po wypróbowaniu wielu wymienionych tutaj rozwiązań zdecydowanie było najłatwiejsze.
Vadorequest

9

Ze debouncetrzeba zachować oryginalną imprezę z syntetycznego wokół event.persist(). Oto sprawdzony przykład działania 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;

Dzięki komponentowi funkcjonalnemu możesz to zrobić -

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

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


1
działa świetnie, najlepsze wdrożenie, jakie do tej pory znalazłem
Vincent Tang,

8

Jeśli używasz redux, możesz to zrobić w bardzo elegancki sposób z oprogramowaniem pośrednim. Można zdefiniować Debounceoprogramowanie pośrednie jako:

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

Następnie możesz dodać debugowanie do twórców akcji, takich jak:

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

W rzeczywistości jest już oprogramowanie pośrednie, które możesz pobrać npm, aby zrobić to za Ciebie.


myślę, że to oprogramowanie pośrednie musi być pierwszym uruchomionym w applyMiddleware(...)łańcuchu, jeśli mamy wiele
Youssef

Limit czasu nie został zainicjowany i ten pierwszy clearTimeout będzie miał do czynienia z niezdefiniowanym parametrem. Niedobrze.
Jason Rice

7

Korzystanie ES6 klasie i React 15.xx & lodash.debounce Im przy reagować na bibl tu od strat Zdarzenie to wiążą wewnętrznie.

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

Wiele dobrych informacji już tutaj, ale by być zwięzłym. To działa dla mnie ...

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

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

To mi nie działa. Stan nie jest aktualizowany. Jeśli usunę _debounce opakowanie, to zadziała. Uwielbiam ten pomysł!
Mote Zart,

Musiałbym zobaczyć twój kod, aby wiele tu zaoferować, ale podejrzewam, że dzieje się coś jeszcze ... mam nadzieję, że ta znacznie dokładniejsza odpowiedź rzuci nieco światła. stackoverflow.com/questions/23123138/…
chad steele

6

Możesz użyć metody debube Lodash https://lodash.com/docs/4.17.5#debounce . To jest proste i skuteczne.

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

Możesz także anulować metodę debounce przy użyciu poniższej metody.

this.debounceHandleUpdate.cancel();

Mam nadzieję, że to ci pomoże. Twoje zdrowie!!


5

Do Twojej wiadomości

Oto kolejna implementacja PoC:

  • bez bibliotek (np. lodash) do ogłaszania
  • za pomocą React Hooks API

Mam nadzieję, że to pomoże :)

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

Istnieje use-debouncepakiet, którego można używać z hakami ReactJS.

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

Jak widać z powyższego przykładu, skonfigurowano aktualizowanie zmiennej valuetylko raz na sekundę (1000 milisekund).


3

Kolejny wariant z najnowszą reakcją i laszowaniem.

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

Próbowałeś?

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

Zamiast zawijać uchwytOnChange w debounce (), dlaczego nie zawijać wywołania ajax wewnątrz funkcji zwrotnej w debounce, nie niszcząc w ten sposób obiektu zdarzenia. Więc coś takiego:

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

4
Ponieważ obiekt zdarzenia nie jest niezmienny i został zniszczony przez ReactJS, więc nawet jeśli zamkniesz i przejdziesz przechwytywanie zamknięcia, kod się nie powiedzie.
Henrik,

2

Oto przykład, który wymyśliłem, który otacza inną klasę debugującym. To ładnie nadaje się do przekształcenia w dekorator / funkcję wyższego rzędu:

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

Istnieje teraz inne rozwiązanie dla React i React Native pod koniec 2019 r . :

składnik Reag-debounce

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

Jest to komponent, łatwy w użyciu, mały i obsługiwany szeroko

Przykład:

wprowadź opis zdjęcia tutaj

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;

* Jestem twórcą tego komponentu


1

Szukałem rozwiązania tego samego problemu i natknąłem się na ten wątek, a także niektóre inne, ale miały one ten sam problem: jeśli próbujesz wykonać handleOnChangefunkcję i potrzebujesz wartości z celu zdarzenia, dostaniesz cannot read property value of nulllub trochę taki błąd. W moim przypadku musiałem także zachować kontekst thisfunkcji, która została zadeklarowana, ponieważ wykonuję płynną akcję. Oto moje rozwiązanie, działa dobrze w moim przypadku użycia, więc zostawiam je tutaj na wypadek, gdyby ktoś natknął się na ten wątek:

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

za throttleczy debouncenajlepszym sposobem jest stworzenie twórca funkcji, dzięki czemu można go używać w każdym miejscu, na przykład:

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

a swoją rendermetodą możesz wykonać:

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

updateUserProfileFieldmetoda stworzy wydzieloną funkcję za każdym razem to nazwać.

Uwaga : nie próbuj zwracać modułu obsługi bezpośrednio, na przykład to nie zadziała:

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

powód, dla którego to nie zadziała, ponieważ spowoduje to wygenerowanie nowej funkcji przepustnicy za każdym razem, gdy wywołane zostanie zdarzenie zamiast używania tej samej funkcji przepustnicy, więc w zasadzie przepustnica będzie bezużyteczna;)

Również, jeśli używasz debouncelub throttlenie potrzebujesz setTimeoutlub clearTimeout, właśnie dlatego ich używamy: P.


1

Oto fragment kodu wykorzystujący podejście @ Abry zawinięte w komponent funkcji (używamy struktury dla interfejsu użytkownika, wystarczy zastąpić go prostym przyciskiem)

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

Moje rozwiązanie jest oparte na hakach (napisane maszynowym skryptem).

Mam 2 główne zaczepy useDebouncedValueiuseDebouncedCallback

Pierwszy - useDebouncedValue

Załóżmy, że mamy pole wyszukiwania, ale chcemy zapytać serwer o wyniki wyszukiwania po tym, jak użytkownik przestanie pisać przez 0,5 s

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

Realizacja

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

druga useDebouncedCallback

Po prostu tworzy funkcję „ogłosiania” w zakresie twojego komponentu.

Powiedzmy, że mamy komponent z przyciskiem, który wyświetli alert 500 ms po tym, jak przestałeś go klikać.

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

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

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

Implementacja (uwaga: używam lodash / debounce jako pomocnika)

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

Oto działający przykład TypeScript dla tych, którzy używają TS i chcą ogłosić asyncfunkcje.

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

trochę tu późno, ale to powinno pomóc. utwórz tę klasę (jest napisana pismem maszynowym, ale łatwo ją przekonwertować na 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;
}

i do użycia

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

0

możesz użyć tlence tlence

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

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

0

Rozwiązanie Julena jest dość trudne do odczytania, tutaj jest bardziej przejrzysty i konkretny kod reagowania dla każdego, kto potknął się o niego na podstawie tytułu, a nie drobnych szczegółów pytania.

wersja tl; dr : kiedy zaktualizujesz do obserwatorów, wyślij zamiast tego wywołanie metody harmonogramu, która z kolei powiadomi obserwatorów (lub wykona ajax itp.)

Uzupełnij jsfiddle przykładowym komponentem 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
Czy nie spowoduje to, że stan / czas debounce będzie globalny we wszystkich instancjach InputField, ponieważ jest on tworzony z definicją klasy? Może to jest to, czego chcesz, ale niezależnie od tego warto to zauważyć.
rabuje

1
niebezpieczne, jeśli zamontowane wiele razy w domu, sprawdź stackoverflow.com/questions/23123138/...
Sebastien Lorber

2
To złe rozwiązanie z powodu problemów z podwójnym montowaniem - ustawiasz funkcję, aby zaplanować Zmień singletona i to nie jest dobry pomysł. -1
Henrik

0

Unikaj używania event.persist()- chcesz pozwolić React przetworzyć syntetyczne zdarzenie. Myślę, że najczystszym sposobem korzystania z klas lub haków jest podzielenie wywołania zwrotnego na dwie części:

  1. Oddzwonienie bez ogłaszania
  2. Wywołuje funkcję zadeklarowaną z tylko potrzebnymi fragmentami zdarzenia (aby zdarzenie syntetyczne można było przetworzyć)

Klasy

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

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

<div onMouseOver={this.onMouseOver} />

Funkcje

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

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

<div onMouseOver={this.onMouseOver} />

Zauważ, że jeśli twoja handleMouseOverfunkcja używa stanu z komponentu, powinieneś użyć useMemozamiast useRefi przekazać je jako zależności, w przeciwnym razie będziesz pracować z nieaktualnymi danymi (nie dotyczy to oczywiście klas).


0

Przedłuż hak useState

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;

Użyj haka

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

Spotkałem ten problem dzisiaj. Rozwiązano to za pomocą setTimeouti clearTimeout.

Podam przykład, który możesz dostosować:

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

Można go również refaktoryzować we własnym komponencie funkcyjnym:

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

I używaj go w następujący sposób:

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.