React.js'de geri dönme gerçekleştir


496

React.js'de debounce nasıl yapıyorsunuz?

HandleOnChange'i geri almak istiyorum.

Denedim debounce(this.handleOnChange, 200)ama işe yaramıyor.

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

Seninle aynı problemle karşılaştım, aşağıda mükemmel cevaplar! ama bence yanlış kullandın debounce. burada, her seferinde onChange={debounce(this.handleOnChange, 200)}/>çağıracak debounce function. ama aslında, ihtiyacımız olan şey, geri alma işlevinin döndürdüğü işlevi çağırmaktır.
pingfengafei

Yanıtlar:


834

2019: Kancaları dene + geri dönme sözü

Bu sorunu nasıl çözeceğimin en güncel sürümüdür. Kullanmak istiyorum:

Bu bazı ilk kablolamadır, ancak kendi başınıza ilkel bloklar oluşturuyorsunuz ve bunu sadece bir kez yapmanız için kendi özel kancanızı yapabilirsiniz.

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

Ve sonra kancayı kullanabilirsiniz:

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

Burada çalışan bu örneği bulacaksınız ve daha fazla bilgi için reaktif asenkron kanca belgelerini okumalısınız .


2018: Geri Dönme Sözünü Deneyin

Arka ucu işe yaramaz isteklerle doldurmamak için API çağrılarını sık sık iptal etmek istiyoruz.

2018'de geri aramalarla çalışmak (Lodash / Underscore) bana kötü ve hataya açık geliyor. Keyfi bir sırayla çözülen API çağrıları nedeniyle ortak plaka ve eşzamanlılık sorunları ile karşılaşmak kolaydır.

Acılarınızı çözmek için React düşünülerek küçük bir kütüphane oluşturdum: harika-debounce-söz .

Bu bundan daha karmaşık olmamalıdır:

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

Çıkarılan fonksiyon şunları sağlar:

  • API çağrıları iptal edilecektir
  • geri alınan işlev her zaman bir söz verir
  • sadece son aramanın iade edilen sözü çözülecek
  • this.setState({ result });API çağrısı başına bir tane olur

Sonunda, bileşeninizin bağlantısı kesilirse başka bir numara ekleyebilirsiniz:

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

Not olduğunu Gözlenebilirler (RxJS) ayrıca girişler zıplamaölçer için ideal olabilir, ancak öğrenmek için zor olabilir daha güçlü bir soyutlama / doğru kullanın.


<2017: hala geri arama iptali özelliğini kullanmak istiyor musunuz?

Buradaki önemli kısım, bileşen örneği başına tek bir kaldırılmış (veya kısaltılmış) işlev oluşturmaktır . Her defasında debounce (veya gaz kelebeği) işlevini yeniden oluşturmak istemezsiniz ve birden çok örneğin aynı debounced işlevini paylaşmasını istemezsiniz.

Bu cevapta gerçekten ilgili olmadığı için bir geri dönme işlevi tanımlamıyorum, ancak bu cevap _.debouncealt çizgi veya lodash'ın yanı sıra kullanıcı tarafından sağlanan herhangi bir geri dönme işleviyle mükemmel şekilde çalışacaktır.


İYİ BİR FİKİR:

Ayrılmış işlevler durumsal olduğundan, bileşen örneği başına bir adet kaldırılmış işlev oluşturmanız gerekir .

ES6 (sınıf özelliği) : önerilir

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

ES6 (sınıf yapıcı)

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

Bkz. JsFiddle : 3 örnek, örnek başına 1 günlük girişi üretmektedir (bu, global olarak 3'tür).


İyi bir fikir değil:

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

Sınıf açıklama nesnesi oluşturma sırasında, thiskendisi oluşturulan nesne değildir çünkü çalışmaz . this.methodbeklediğiniz şeyi döndürmez, çünkü thisbağlam nesnenin kendisi değildir (gerçekte henüz yaratılmakta olan BTW henüz mevcut değildir).


İyi bir fikir değil:

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

Bu kez etkin bir şekilde sizin çağrı çağıran bir işlevi yaratırsınız this.method. Sorun şu ki, her debouncedMethodçağrıda yeniden oluşturuyorsunuz, bu yüzden yeni oluşturulan geri dönme fonksiyonu eski çağrılar hakkında hiçbir şey bilmiyor! Zamanla aynı geri dönen işlevi yeniden kullanmalısınız, aksi takdirde geri alma gerçekleşmez.


İyi bir fikir değil:

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

Bu biraz zor.

Sınıfın tüm monte edilmiş örnekleri aynı ayrılmış işlevi paylaşacaktır ve çoğu zaman bu istediğiniz şey değildir !. Bkz. JsFiddle : Dünya çapında 3 örnek yalnızca 1 günlük girişi üretiyor .

Her bileşen örneği tarafından paylaşılan, sınıf düzeyinde tek bir debounced işlevi değil, her bileşen örneği için bir debounced işlevi oluşturmanız gerekir .


React'in etkinlik havuzuna dikkat edin

Bu, DOM etkinliklerini sık sık kaldırmak veya kısıtlamak istediğimiz için ilgilidir.

React'te, SyntheticEventgeri çağrılarda aldığınız olay nesneleri (ör. ) Toplanır (bu artık belgelenmiştir ). Bu, olay geri çağrısı çağrıldıktan sonra, aldığınız SyntheticEvent'in GC basıncını azaltmak için boş niteliklerle havuza geri konulacağı anlamına gelir.

Bu nedenle, SyntheticEventözelliklere orijinal geri aramayla eşzamansız olarak erişirseniz (kısma / yeniden başlatma durumunda olduğu gibi), eriştiğiniz özellikler silinebilir. Etkinliğin bir daha asla havuza konmamasını istiyorsanız, persist()yöntemi kullanabilirsiniz .

Kalıcı olmadan (varsayılan davranış: havuzlanmış olay)

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

hasNativeEvent=falseEtkinlik özellikleri temizlendiği için 2. (zaman uyumsuz) yazdırılır .

Kalıcı olarak

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

2. (zaman uyumsuz) yazdırılacaktır, hasNativeEvent=trueçünkü persistetkinliği tekrar havuza koymaktan kaçınmanıza olanak tanır.

Bu 2 davranışı burada test edebilirsiniz: JsFiddle

Gaz kelebeği / debounce işleviyle kullanım örneği için Julen'in cevabını okuyun persist().


3
Mükemmel yanıt, bu, form alanlarının durumunu yazmayı bıraktıktan sonra birkaç saniye boyunca 'etkileşim' olarak ayarlamak ve daha sonra form
gönderimi

8
ES6'da, yönteminizi yapıcı içinde tanımlamak yerine (garip hissettirmek yerine) handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)sınıfınızın en üst düzeyinde yapabileceğinizi unutmayın. Hâlâ etkin bir örnek üyesi oluşturuyorsunuz, ancak normal yöntem tanımına biraz benziyor. Bir gerek yok constructorzaten tanımlanmış bir tane yoksa. Sanırım çoğunlukla bir stil tercihi.
thom_nic

24
Çıkarılan yöntemi iptal etmeyi unutmayın componentWillUnmount: this.method.cancel()- aksi takdirde bağlanmamış bir bileşende setState öğesini ayarlamak isteyebilir.
elado

4
@JonasKello, durum bilgisi olmayan bir bileşenin içinde geri dönemezsiniz, çünkü geri alınan işlev aslında durumsaldır. Bu ayrılmış işlevi tutmak için durum bilgisi olan bir bileşene ihtiyacınız vardır, ancak gerekirse zaten ayrılmış işlevi olan durum bilgisi olmayan bir bileşeni çağırabilirsiniz.
Sebastien Lorber

2
Neden tüm cevap işlevi yazmak yerine _.debounce içeriyor? Bu işlev için tüm kütüphaneye mi ihtiyacı var?
chifliiiii

217

Kontrolsüz Bileşenler

event.persist()Yöntemi kullanabilirsiniz .

Alt çizgi kullanarak bir örnek _.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} />
    );
  }

});

Düzenleme: Bu JSFiddle'a bakın


Kontrollü Bileşenler

Güncelleme: yukarıdaki örnek kontrolsüz bir bileşeni göstermektedir . Her zaman kontrollü elemanlar kullanıyorum, bu yüzden yukarıdakilerin başka bir örneği, ancak event.persist()"hile" kullanmadan.

Bir JSFiddle da mevcuttur . Altçizgi olmayan örnek

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

Düzenleme: Güncellenmiş örnekler ve Reaksiyon 0.12 JSFiddles

Düzenleme: Sebastien Lorber'in gündeme getirdiği sorunu gidermek için güncellenmiş örnekler

Düzenleme: alt çizgi kullanmayan ve düz javascript açılma kullanan jsfiddle ile güncellendi.


Bu girişler için çalışmaz. Ayrılmış işlevdeki olay hedefi artık bir değere sahip değil ... bu nedenle giriş boş kalır.
Etai

1
Biraz karmaşık, bu. Dikmeler konusunda biraz dikkatli olmalısınız. Ayar yaparsanız <input value={this.props.someprop}..., tuşa basmadaki güncelleştirme geri dönme işleminden sonraya kadar bileşene geri getirmediğinden düzgün bir şekilde görüntülenmez. Bunun value=yönetilmesinden memnunsanız, değeri önceden doldurmak ve / veya başka bir yere bağlamak istiyorsanız, o zaman bu işe yaramaz.
Alastair Maw

1
@AlastairMaw sorusunun kontrolsüz bir bileşeni vardı, bu yüzden cevapta da var. Önceden doldurulmuş bir değere sahip, kontrollü bileşenler için alternatif bir sürümün altına ekledim.
julen

2
Bileşeni
Sebastien Lorber

4
Bu harika bir yanıt olsa da, persistözellikle gibi birçok etkinlik olduğunda kullanmanızı önermiyorum mousemove. Kodun bu şekilde tamamen tepkisiz hale geldiğini gördüm. Olay çağrısında yerel olaydan gerekli verileri ayıklamak ve daha sonra etkinliğin kendisiyle DEĞİL, yalnızca veriyle geri dönen / daraltılmış işlevi çağırmak çok daha etkilidir. Etkinliği bu şekilde
sürdürmeye

31

2019: 'useCallback' tepki kancasını kullan

Birçok farklı yaklaşımı denedikten sonra, bir olayda useCallbackbirden fazla çağrı problemini çözmede en basit ve en etkili yöntemi kullanarak buldum .debounceonChange

Gereğince Kancalar API belgelerinde ,

useCallback geri aramanın yalnızca bağımlılıklardan biri değiştiğinde değişen ezberlenmiş bir sürümünü döndürür.

Boş bir diziyi bağımlılık olarak geçirmek, geri aramanın yalnızca bir kez çağrılmasını sağlar. İşte basit bir uygulama:

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

Bu yardımcı olur umarım!


3
Kanca kullanıyorsanız mükemmel çözüm. Bana saatler süren hayal kırıklığını kurtardın. Teşekkürler!
Carl Edwards

Birden fazla aramanın neden ilk etapta gerçekleştiğini açıklayabilir misiniz? Geri aramanın aynı geri arama yöntemi debounce()olduğunu düşünmüyor mu onChange()?
El Anonimo

Uygulamamda çalışmasını sağlamak için bu çözümü değiştirdim. Önce çizgiyi const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);fonksiyon bileşeninin gövdesine taşımak zorunda kaldım ya da React bunun dışında kanca kullanımı hakkında bir hata mesajı verir. Sonra içinde onChangeolay işleyicisi: <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.
El Anonimo

İşte bu girdiyi, bir girdiye kullanıcı türünün girmesini ve ardından yazmayı bitirdikten sonra giriş değeriyle bir debounced API çağrısı göndermesini sağlamak için bu çözümü kullandım. stackoverflow.com/questions/59358092/… .
El Anonimo

14

Justin Tulk'un bu yazısını çok yardımcı buldum . Birkaç denemeden sonra, reaksiyon / redux ile daha resmi bir yol olarak algılanacak olan şey, React'in sentetik olay havuzlaması nedeniyle başarısız olduğunu gösterir . Onun çözümü daha sonra girişte değiştirilen / girilen değeri izlemek için bazı dahili durumları kullanır, hemen sonra bir geri arama ile setState, gerçek zamanlı olarak bazı sonuçları gösteren kısılmış / kaldırılmış bir redux eylemini çağırır.

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

Olay nesnesinden ihtiyacınız olan tek şey DOM giriş öğesini elde etmekse, çözüm çok daha basittir - sadece kullanın ref. Bunun Alt Çizgi gerektirdiğini unutmayın :

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 istediğim şey! Çok teşekkür ederim mach :)
Tazo leladze

14

Metin girişleriyle bir süre uğraştıktan ve kendi başıma mükemmel bir çözüm bulamadan sonra bunu npm'de buldum: tepki- debounce -giriş .

İşte basit bir örnek:

import React from 'react';
import ReactDOM from 'react-dom';
import {DebounceInput} from 'react-debounce-input';

class App extends React.Component {
state = {
    value: ''
};

render() {
    return (
    <div>
        <DebounceInput
        minLength={2}
        debounceTimeout={300}
        onChange={event => this.setState({value: event.target.value})} />

        <p>Value: {this.state.value}</p>
    </div>
    );
}
}

const appRoot = document.createElement('div');
document.body.appendChild(appRoot);
ReactDOM.render(<App />, appRoot);

DebounceInput bileşeni, normal bir giriş öğesine atayabileceğiniz tüm destekleri kabul eder. Kod üzerinde deneyin

Umarım başka birine de yardımcı olur ve zaman kazandırır.


Burada listelenen birçok çözümü denedikten sonra, kesinlikle en kolayı oldu.
Vadorequest

9

İle debouncesen etrafında orijinal sentetik olayı tutmak için gereken event.persist(). İşte test edilen çalışma örneği 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;

İşlevsel bileşenle bunu yapabilirsiniz -

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

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


1
şimdiye kadar buldum harika en iyi uygulama çalışır
Vincent Tang

8

Eğer redux kullanıyorsanız, bunu ara katman yazılımı ile çok zarif bir şekilde yapabilirsiniz. Bir Debounceara katman yazılımını şu şekilde tanımlayabilirsiniz :

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

Daha sonra, aşağıdakileri içeren eylem oluşturuculara geri dönme ekleyebilirsiniz:

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

Aslında zaten sizin için bunu yapmak için npm alabilirsiniz orta katman var.


Bence bu ara katman applyMiddleware(...)zincirinde ilk çalıştırılacak olması gerektiğini düşünüyorum
Youssef

Zaman aşımı başlatılmaz ve ilk clearTimeout bir param için tanımsız ile ilgilenir. İyi değil.
Jason Rice

7

ES6 SINIF kullanma ve 15.xx tepki en Tepki kullanarak & lodash.debounce Im ref içten bu bağlama olayı kayıpları beri burada.

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

Zaten burada iyi bilgi Lots, ama özlü olmak. Bu benim için çalışıyor ...

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

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

Bu benim için işe yaramıyor. Durum güncellenmiyor. _debounce Sarıcıyı çıkarırsam çalışır. Yine de bu fikri seviyorum!
Mote Zart

Burada çok şey sunmak için kodunuzu görmek zorundayım, ama devam eden başka bir şey olduğundan şüpheleniyorum ... umarım bu çok daha kapsamlı cevap biraz ışık tutacaktır. stackoverflow.com/questions/23123138/…
chad steele

6

Lodash debounce https://lodash.com/docs/4.17.5#debounce yöntemini kullanabilirsiniz . Basit ve etkilidir.

import * as lodash from lodash;

const update = (input) => {
    // Update the input here.
    console.log(`Input ${input}`);     
}

const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200});

doHandleChange() {
   debounceHandleUpdate(input);
}

Debounce yöntemini aşağıdaki yöntemi kullanarak da iptal edebilirsiniz.

this.debounceHandleUpdate.cancel();

Umarım size yardımcı olur. Alkış !!


5

Bilginize

İşte başka bir PoC uygulaması:

  • herhangi bir kütüphane olmadan (örneğin, lodash)
  • React Hooks API'sını kullanma

Umut ediyorum bu yardım eder :)

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

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

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

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

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

4

use-debounceReactJS kancalarıyla kullanabileceğiniz bir paket var.

Paketin README'sinden:

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

Yukarıdaki örnekte de görebileceğiniz gibi, değişkeni valueyalnızca saniyede bir (1000 milisaniye) güncellemek üzere ayarlanmıştır .


3

Son zamanlarda tepki veren ve sersemleyen bir başka varyant.

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

Denedin mi?

function debounce(fn, delay) {
  var timer = null;
  return function() {
    var context = this,
      args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

var SearchBox = React.createClass({
  render: function() {
    return <input type="search" name="p" onChange={this.handleOnChange} />;
  },

  handleOnChange: function(event) {
    debounce(\\ Your handleChange code , 200);
  }
});

2

HandleOnChange öğesini bir debounce () öğesine sarmak yerine neden ajax çağrısını debounce içindeki geri arama işlevinin içine sarmıyorsunuz, dolayısıyla olay nesnesini yok etmiyorsunuz. Yani böyle bir şey:

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

4
Olay nesnesi değişmez olmadığından ve ReactJS tarafından yok edildiğinden, bir kapatma yakalaması sarsanız ve yakalasanız bile kod başarısız olur.
Henrik

2

İşte başka bir sınıfı bir debouncer ile saran bir örnek. Bu, bir dekoratör / daha yüksek sipariş işlevine dönüştürülmesine güzel bir şekilde katkıda bulunur:

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

Artık 2019'un sonlarında React and React Native için başka bir çözüm var :

tepki-filtreleme bileşenli

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

Bileşen, kullanımı kolay, küçük ve widley destekli

Misal:

resim açıklamasını buraya girin

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;

* Ben bu bileşenin yaratıcısıyım


1

Aynı soruna bir çözüm arıyordum ve diğerlerinin yanı sıra bu iş parçacığına rastladım ama aynı sorunu vardı: bir handleOnChangeişlev yapmaya çalışıyorsanız ve bir olay hedefinden değere ihtiyacınız varsa, alacaksınız cannot read property value of nullveya bazı böyle bir hata. Benim durumumda, thisakabilir bir eylem yürüttüğüm için debounced fonksiyonunun içeriğini de korumam gerekiyordu. İşte benim çözüm, benim kullanım dava için iyi çalışıyor, bu yüzden kimse bu iş parçacığı rastlamak durumunda burada bırakarak:

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

for throttleveya debounceen iyi yol, bir işlev yaratıcısı oluşturmaktır, böylece herhangi bir yerde kullanabilirsiniz, örneğin:

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

ve renderyönteminizde şunları yapabilirsiniz:

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

updateUserProfileFieldyöntem ayrılmış fonksiyonunu diyorsunuz her zaman yaratacaktır.

Not işleyiciyi doğrudan döndürmeye çalışmayın, örneğin bu işe yaramaz:

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

bunun neden işe yaramayacağının nedeni, olay aynı gaz kelebeği işlevini kullanmak yerine her çağrıldığında yeni bir gaz kelebeği işlevi üreteceğinden, temel olarak gaz kelebeği işe yaramaz;)

Ayrıca kullanırsanız debounceveya throttleihtiyacınız setTimeoutyoksa veya clearTimeoutaslında, bu yüzden onları kullanıyoruz: P


1

İşte @ Abra'nın bir işlev bileşenine sarılmış yaklaşımını kullanan bir snippet (UI için kumaş kullanıyoruz, sadece basit bir düğmeyle değiştiriyoruz)

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

Benim çözümüm kanca tabanlı (daktiloda yazılmış).

2 ana kanca var useDebouncedValueveuseDebouncedCallback

İlk - useDebouncedValue

Diyelim ki bir arama kutumuz var, ancak kullanıcı 0,5 sn yazmayı bıraktıktan sonra sunucudan arama sonuçlarını istemek istiyoruz

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

uygulama

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

İkinci useDebouncedCallback

Bileşeninizin kapsamı içinde sadece "kaldırılmış" bir işlev oluşturur.

Diyelim ki tıkladıktan sonra 500 ms uyarısı gösteren düğmeli bir bileşenimiz var.

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

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

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

Uygulama (bir yardımcı olarak lodash / debounce kullandığımı unutmayın)

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

İşte TS kullanan ve asyncişlevleri geri almak isteyen kullanıcılar için çalışan bir TypeScript örneği .

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

burada biraz geç ama bu yardımcı olacaktır. Bu sınıfı yarat

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

ve kullanmak

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

0

şiddeti kullanabilirsin şiddeti

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

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

0

Julen çözümünün okunması biraz zor, işte, sorunun küçük ayrıntılarını değil, başlığa göre tökezleyen herkes için daha açık ve noktaya tepki kodu.

tl; dr sürümü : gözlemcilere güncelleme yaptığınızda bunun yerine bir zamanlama yöntemi çağırın ve gözlemcileri gerçekten bilgilendirir (veya ajax vb. gerçekleştirir)

Örnek jsfiddle bileşeniyle eksiksiz 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
Sınıf tanımıyla oluşturulduğu için bu, InputField'ın tüm örneklerinde geri dönmenin durumunu / zamanlamasını global yapmaz mı? Belki de bunu istersiniz, ama ne olursa olsun kayda değer.
14'te robbles

1
Dom'a birden çok kez monte edilmişse tehlikeli, stackoverflow.com/questions/23123138/…
Sebastien Lorber

2
Bu, çift montajlı sorunlar nedeniyle kötü bir çözümdür - scheduleChanton'u tek bir ton değiştirme işlevini yapıyorsunuz ve bu iyi bir fikir değil. -1
Henrik

0

Kullanmaktan kaçının event.persist()- React'in sentetik olayı geri dönüştürmesine izin vermek istersiniz. Sınıfları veya kancaları kullanmanın en temiz yolunun geri aramayı iki parçaya ayırmak olduğunu düşünüyorum:

  1. Ayrılmadan geri arama
  2. Sadece ihtiyacınız olan olayın parçalarıyla geri dönen bir işlevi çağırır (böylece sentetik etkinlik geri dönüştürülebilir)

Sınıflar

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

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

<div onMouseOver={this.onMouseOver} />

Fonksiyonlar

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

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

<div onMouseOver={this.onMouseOver} />

Senin eğer Not handleMouseOverfonksiyon bileşeni içinden devlet kullanır, kullanmak gerekir useMemoyerine useRefve aksi takdirde eski verilerle çalışacak bağımlılıkları sıra bu geçmesine (tabii ki sınıflar için geçerli değildir).


0

Kullanım kancası

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;

Kanca kullan

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

Bugün bu problemle karşılaştı. setTimeoutVe kullanarak çözüldüclearTimeout .

Uyarlayabileceğiniz bir örnek vereceğim:

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

Ayrıca, kendi işlev bileşeninde yeniden düzenleyebilirsiniz:

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

Ve şöyle kullanın:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.