Lakukan debounce di React.js


496

Bagaimana Anda melakukan debounce di React.js?

Saya ingin melemahkan handleOnChange.

Saya sudah mencoba debounce(this.handleOnChange, 200)tetapi tidak berhasil.

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

Saya bertemu dengan masalah yang sama dengan Anda, jawaban luar biasa di bawah ini! tapi saya pikir Anda menggunakan cara yang salah debounce. di sini, kapan onChange={debounce(this.handleOnChange, 200)}/>, itu akan memanggil debounce functionsetiap waktu. tetapi, pada kenyataannya, yang kita butuhkan adalah memanggil fungsi apa fungsi debounce dikembalikan.
pingfengafei

Jawaban:


835

2019: coba kait + janji tolak

Ini adalah versi terbaru tentang bagaimana saya akan menyelesaikan masalah ini. Saya akan menggunakan:

Ini adalah beberapa pengkabelan awal tetapi Anda membuat blok primitif sendiri, dan Anda dapat membuat kait kustom Anda sendiri sehingga Anda hanya perlu melakukan ini satu kali.

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

Dan kemudian Anda bisa menggunakan hook Anda:

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

Anda akan menemukan contoh ini berjalan di sini dan Anda harus membaca dokumentasi react-async-hook untuk lebih jelasnya.


2018: cobalah berjanji untuk melonggarkan

Kami sering ingin mendebo panggilan API untuk menghindari membanjiri backend dengan permintaan yang tidak berguna.

Pada tahun 2018, bekerja dengan callback (Lodash / Underscore) terasa buruk dan rentan kesalahan bagi saya. Sangat mudah untuk menemukan masalah boilerplate dan konkurensi karena penyelesaian panggilan API dalam urutan arbitrer.

Saya telah membuat perpustakaan kecil dengan React dalam pikiran untuk menyelesaikan rasa sakit Anda: mengagumkan-debounce-janji .

Ini seharusnya tidak lebih rumit dari itu:

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

Fungsi yang didebok memastikan bahwa:

  • Panggilan API akan dideboed
  • fungsi yang didebo selalu mengembalikan janji
  • hanya janji panggilan terakhir yang akan menyelesaikan
  • satu this.setState({ result });akan terjadi per panggilan API

Akhirnya, Anda dapat menambahkan trik lain jika komponen Anda dilepas:

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

Perhatikan bahwa Observables (RxJS) juga bisa sangat cocok untuk mendebit input, tetapi ini adalah abstraksi yang lebih kuat yang mungkin lebih sulit untuk dipelajari / digunakan dengan benar.


<2017: masih ingin menggunakan pembatalan panggilan balik?

Bagian penting di sini adalah membuat fungsi debouncing tunggal (atau dibatasi) per instance komponen . Anda tidak ingin membuat ulang fungsi debounce (atau throttle) setiap kali, dan Anda tidak ingin salah satu dari beberapa instance berbagi fungsi debounce yang sama.

Saya tidak mendefinisikan fungsi debouncing dalam jawaban ini karena itu tidak benar-benar relevan, tetapi jawaban ini akan berfungsi dengan baik dengan _.debouncegaris bawah atau lodash, serta fungsi debouncing yang disediakan pengguna.


IDE BAGUS:

Karena fungsi yang di-debo-kan adalah stateful, kita harus membuat satu fungsi yang di-debo-kan per instance komponen .

ES6 (properti kelas) : direkomendasikan

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

ES6 (konstruktor kelas)

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

Lihat JsFiddle : 3 instance menghasilkan 1 entri log per instance (yang menghasilkan 3 global).


BUKAN ide yang bagus:

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

Ini tidak akan berfungsi, karena selama pembuatan objek deskripsi kelas, thisbukan objek yang dibuat sendiri. this.methodtidak mengembalikan apa yang Anda harapkan karena thiskonteksnya bukan objek itu sendiri (yang sebenarnya belum benar-benar ada BTW karena baru saja dibuat).


BUKAN ide yang bagus:

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

Kali ini Anda secara efektif membuat fungsi terbebas yang memanggil Anda this.method. Masalahnya adalah Anda membuat ulang pada setiap debouncedMethodpanggilan, sehingga fungsi debounce yang baru dibuat tidak tahu apa-apa tentang panggilan sebelumnya! Anda harus menggunakan kembali fungsi debouncing yang sama dari waktu ke waktu atau debouncing tidak akan terjadi.


BUKAN ide yang bagus:

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

Ini agak sulit di sini.

Semua instance yang terpasang dari kelas akan berbagi fungsi yang sama, dan paling sering ini bukan yang Anda inginkan !. Lihat JsFiddle : 3 instance hanya menghasilkan 1 entri log secara global.

Anda harus membuat fungsi terbebas untuk setiap instance komponen , dan bukan fungsi terbebas tunggal di tingkat kelas, dibagikan oleh setiap instance komponen.


Jaga pooling acara React

Ini terkait karena kita sering ingin men-debo atau mencekik acara DOM.

Di Bereaksi, objek acara (yaitu, SyntheticEvent) yang Anda terima di panggilan balik dikumpulkan (ini sekarang didokumentasikan ). Ini berarti bahwa setelah event callback dipanggil, SyntheticEvent yang Anda terima akan dimasukkan kembali ke kolam dengan atribut kosong untuk mengurangi tekanan GC.

Jadi, jika Anda mengakses SyntheticEventproperti secara tidak sinkron ke callback asli (seperti yang terjadi jika Anda mencekik / debounce), properti yang Anda akses dapat dihapus. Jika Anda ingin acara tersebut tidak dimasukkan kembali ke dalam kolam, Anda dapat menggunakan persist()metode ini.

Tanpa bertahan (perilaku default: acara gabungan)

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

Kedua (async) akan mencetak hasNativeEvent=falsekarena properti acara telah dibersihkan.

Dengan bertahan

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

Kedua (async) akan mencetak hasNativeEvent=truekarenapersist memungkinkan Anda untuk menghindari meletakkan kembali acara di kolam.

Anda dapat menguji 2 perilaku ini di sini: JsFiddle

Baca jawaban Julen untuk contoh penggunaan persist()dengan fungsi throttle / debounce.


3
Jawaban luar biasa, ini bagus untuk menyetel bidang isian formulir sebagai 'berinteraksi' selama beberapa detik setelah berhenti mengetik, dan kemudian dapat membatalkan pengiriman formulir atau diBlur
arush_try.com

8
Perhatikan bahwa dalam ES6, alih-alih mendefinisikan metode Anda di dalam konstruktor (terasa aneh), Anda dapat melakukannya handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)di tingkat atas kelas Anda. Anda masih secara efektif menetapkan anggota instance tetapi terlihat sedikit lebih seperti definisi metode normal. Tidak perlu untuk constructorjika Anda belum memilikinya. Saya kira itu sebagian besar preferensi gaya.
thom_nic

24
Jangan lupa untuk membatalkan metode debouncing di componentWillUnmount: this.method.cancel()- jika tidak, mungkin ingin mengaturState pada komponen unmount.
Elado

4
@JonasKello Anda tidak dapat melakukan debounce di dalam komponen stateless karena fungsi debounce sebenarnya stateful. Anda memerlukan komponen stateful untuk menahan fungsi yang didebok itu, tetapi Anda bisa memanggil komponen stateless dengan fungsi yang sudah didebokkan jika diperlukan.
Sebastien Lorber

2
Mengapa semua jawaban menyertakan _.debounce alih-alih menulis fungsi? Perlu seluruh perpustakaan untuk fungsi itu?
chifliiiii

217

Komponen yang Tidak Terkendali

Anda dapat menggunakan event.persist()metode ini .

Contoh berikut menggunakan garis bawah _.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} />
    );
  }

});

Sunting: Lihat JSFiddle ini


Komponen Terkendali

Pembaruan: contoh di atas menunjukkan komponen yang tidak terkontrol . Saya menggunakan elemen terkontrol sepanjang waktu jadi inilah contoh lain di atas, tetapi tanpa menggunakanevent.persist() "tipuan".

Sebuah JSFiddle tersedia juga. Contoh tanpa garis bawah

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: contoh diperbarui dan JSFiddles untuk Bereaksi 0,12

Sunting: contoh diperbarui untuk mengatasi masalah yang diangkat oleh Sebastien Lorber

Sunting: diperbarui dengan jsfiddle yang tidak menggunakan garis bawah dan menggunakan debounce javascript biasa.


Ini tidak berfungsi untuk input. Target acara dalam fungsi yang di-debounce tidak lagi memiliki nilai ... sehingga input tetap kosong.
Etai

1
Agak rumit, ini. Anda harus sedikit berhati-hati dengan alat peraga. Jika Anda mengaturnya <input value={this.props.someprop}...maka itu tidak akan ditampilkan dengan benar karena pembaruan pada penekanan tombol tidak membuatnya kembali ke komponen sampai setelah debounce. Tidak masalah untuk menghapus value=jika Anda senang ini tidak dikelola, tetapi jika Anda ingin mempopulasikan nilai dan / atau mengikatnya di tempat lain maka jelas ini tidak berhasil.
Alastair Maw

1
@AlastairMaw pertanyaan memiliki komponen yang tidak terkendali, itu sebabnya jawabannya juga. Saya telah menambahkan di bawah versi alternatif untuk komponen yang dikendalikan, dengan nilai yang sudah diisi sebelumnya.
julen

2
ini sangat berbahaya jika Anda memasang komponen mutiple kali di DOM, lihat stackoverflow.com/questions/23123138/…
Sebastien Lorber

4
Meskipun ini adalah jawaban yang bagus, saya tidak merekomendasikan penggunaan persistterutama ketika mungkin ada banyak acara, seperti pada mousemove. Saya telah melihat kode menjadi benar-benar tidak responsif seperti itu. Jauh lebih efisien untuk mengekstrak data yang diperlukan dari acara asli dalam panggilan acara, dan kemudian memanggil fungsi debouncing / dibatasi dengan data saja, BUKAN acara itu sendiri. Tidak perlu
melanjutkan

31

2019: Gunakan kait reaksi 'useCallback'

Setelah mencoba banyak pendekatan yang berbeda, saya menemukan menggunakan useCallbackmenjadi yang paling sederhana dan paling efisien untuk menyelesaikan masalah beberapa panggilan menggunakan debouncedalam suatu onChangeperistiwa.

Sesuai dokumentasi Hooks API ,

useCallback mengembalikan versi panggilan balik yang dihafal yang hanya berubah jika salah satu dari dependensi telah berubah.

Melewati array kosong sebagai ketergantungan memastikan callback dipanggil hanya sekali. Berikut ini adalah implementasi sederhana:

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

Semoga ini membantu!


3
Solusi luar biasa jika Anda menggunakan kait. Anda menyelamatkan saya lebih banyak jam frustrasi. Terima kasih!
Carl Edwards

Bisakah Anda jelaskan mengapa beberapa panggilan terjadi? Tidak debounce()menganggap onChange()callback sebagai metode panggilan balik yang sama?
El Anonimo

Saya memodifikasi solusi ini agar berfungsi di aplikasi saya. Pertama saya harus memindahkan garis const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);di dalam tubuh komponen fungsi atau Bereaksi output pesan kesalahan tentang penggunaan hook di luar itu. Kemudian pada onChangeevent handler: <input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}.
El Anonimo

Berikut adalah cara saya menggunakan solusi ini untuk memungkinkan pengguna mengetik ke input lalu mengirim panggilan API yang dideboun dengan nilai input begitu dia selesai mengetik. stackoverflow.com/questions/59358092/… .
El Anonimo

14

Saya menemukan posting ini oleh Justin Tulk sangat membantu. Setelah beberapa upaya, dalam apa yang orang anggap sebagai cara yang lebih resmi dengan reaksi / reduks, itu menunjukkan bahwa itu gagal karena penyatuan acara sintetis React . Solusinya kemudian menggunakan beberapa keadaan internal untuk melacak nilai yang diubah / dimasukkan dalam input, dengan panggilan balik setelah setStateitu memanggil tindakan redux yang dibatasi / dideboed yang menunjukkan beberapa hasil secara realtime.

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

Jika yang Anda butuhkan dari objek acara adalah untuk mendapatkan elemen input DOM, solusinya jauh lebih sederhana - cukup gunakan ref. Perhatikan bahwa ini memerlukan Garis Bawah :

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 adalah apa yang saya inginkan! Terima kasih, sangat mach :)
Tazo leladze

14

Setelah berjuang dengan input teks untuk sementara waktu dan tidak menemukan solusi yang sempurna pada saya sendiri, saya menemukan ini di npm: react-debounce-input .

Ini adalah contoh sederhana:

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

Komponen DebounceInput menerima semua alat peraga yang dapat Anda tetapkan untuk elemen input normal. Cobalah di codepen

Saya harap ini membantu orang lain juga dan menghemat waktu mereka.


Setelah mencoba banyak solusi yang tercantum di sini, pastilah yang paling mudah.
Vadorequest

9

Dengan debounceAnda perlu menjaga acara sintetis asli dengan event.persist(). Berikut ini contoh kerja yang diuji 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;

Dengan komponen fungsional, Anda dapat melakukan ini -

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

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


1
bekerja implementasi terbaik yang sangat baik yang saya temukan sejauh ini
Vincent Tang

8

Jika Anda menggunakan redux, Anda bisa melakukan ini dengan cara yang sangat elegan dengan middleware. Anda dapat mendefinisikan Debouncemiddleware sebagai:

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

Anda kemudian dapat menambahkan debouncing ke pembuat tindakan, seperti:

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

Sebenarnya sudah ada middleware Anda bisa keluar npm untuk melakukan ini untuk Anda.


Saya pikir middleware ini harus menjadi yang pertama dieksekusi dalam applyMiddleware(...)rantai jika kita memiliki banyak
Youssef

Timeout tidak diinisialisasi dan clearTimeout pertama akan berhadapan dengan undefined untuk param. Tidak baik.
Jason Rice

7

Menggunakan ES6 CLASS dan React 15.xx & lodash.debounce Saya menggunakan referensi React di sini karena peristiwa kehilangan ini mengikat secara internal.

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

Banyak info bagus sudah ada di sini, tetapi harus singkat. Ini bekerja untuk saya ...

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

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

Ini tidak berhasil untuk saya. Status tidak diperbarui. Jika saya menghapus _debounce pembungkusnya berfungsi. Saya suka ide ini!
Mote Zart

Saya harus melihat kode Anda untuk menawarkan banyak di sini, tapi saya curiga ada sesuatu yang terjadi ... mudah-mudahan jawaban yang lebih menyeluruh ini akan memberi sedikit cahaya. stackoverflow.com/questions/23123138/…
chad steele

6

Anda dapat menggunakan Lodash debounce https://lodash.com/docs/4.17.5#debounce metode. Ini sederhana dan efektif.

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

Anda juga dapat membatalkan metode debounce dengan menggunakan metode di bawah ini.

this.debounceHandleUpdate.cancel();

Semoga ini bisa membantu Anda. Bersulang!!


5

FYI

Berikut ini adalah implementasi PoC lainnya:

  • tanpa pustaka (mis. Lodash) untuk debouncing
  • menggunakan React Hooks API

Saya harap ini membantu :)

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

Ada use-debouncepaket yang bisa Anda gunakan dengan kait ReactJS.

Dari README paket:

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

Seperti yang Anda lihat dari contoh di atas, sudah diatur untuk memperbarui variabel valuehanya sekali setiap detik (1000 milidetik).


3

Hanya varian lain dengan reaksi dan lodash terbaru.

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

Apakah kamu sudah mencoba?

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

Alih-alih membungkus handleOnChange dalam debounce (), mengapa tidak membungkus panggilan ajax di dalam fungsi callback di dalam debounce, sehingga tidak merusak objek acara. Jadi sesuatu seperti ini:

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

4
Karena objek acara tidak dapat diubah dan dihancurkan oleh ReactJS, jadi bahkan jika Anda membungkus dan mendapatkan tangkapan penutup, kode tersebut akan gagal.
Henrik

2

Berikut adalah contoh yang saya buat dengan membungkus kelas lain dengan debouncer. Ini cocok untuk dibuat menjadi dekorator / fungsi tingkat tinggi:

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

Sekarang ada solusi lain untuk Bereaksi dan Bereaksi Asli pada akhir / 2019 :

bereaksi-debounce-komponen

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

Ini adalah komponen, mudah digunakan, mungil dan didukung widley

Contoh:

masukkan deskripsi gambar di sini

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;

* Saya pencipta komponen ini


1

Saya sedang mencari solusi untuk masalah yang sama dan menemukan utas ini serta beberapa yang lain tetapi mereka memiliki masalah yang sama: jika Anda mencoba untuk melakukan handleOnChangefungsi dan Anda membutuhkan nilai dari target acara, Anda akan mendapatkan cannot read property value of nullatau kesalahan seperti itu. Dalam kasus saya, saya juga perlu menjaga konteks thisdi dalam fungsi debouncing karena saya sedang melakukan tindakan fluxible. Inilah solusi saya, ini berfungsi dengan baik untuk kasus penggunaan saya jadi saya meninggalkannya di sini kalau-kalau ada yang datang di utas ini:

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

untuk throttleatau debouncecara terbaik adalah membuat pencipta fungsi sehingga Anda dapat menggunakannya di mana saja, misalnya:

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

dan dalam rendermetode Anda, Anda dapat melakukan:

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

yang updateUserProfileFieldmetode akan membuat fungsi terpisah setiap kali Anda menyebutnya.

Catatan jangan coba mengembalikan pawang secara langsung karena ini tidak akan berfungsi:

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

alasan mengapa ini tidak akan berhasil karena ini akan menghasilkan fungsi throttle baru setiap kali acara dipanggil alih-alih menggunakan fungsi throttle yang sama, jadi pada dasarnya throttle tidak akan berguna;)

Juga jika Anda menggunakan debounceatau throttletidak perlu setTimeoutatau clearTimeout, inilah sebabnya kami menggunakannya: P


1

Berikut cuplikan menggunakan pendekatan @ Abra yang dibungkus dengan komponen fungsi (kami menggunakan fabric untuk UI, cukup ganti dengan tombol sederhana)

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

Solusi saya adalah berbasis kait (ditulis dalam naskah).

Saya punya 2 kait utama useDebouncedValuedanuseDebouncedCallback

Pertama - useDebouncedValue

Katakanlah kita memiliki kotak pencarian, tetapi kami ingin meminta server untuk hasil pencarian setelah pengguna berhenti mengetik 0,5s

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

Penerapan

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

Kedua useDebouncedCallback

Itu hanya menciptakan fungsi 'terbebas' dalam lingkup komponen Anda.

Katakanlah kita memiliki komponen dengan tombol yang akan menampilkan peringatan 500 ms setelah Anda berhenti mengkliknya.

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

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

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

Implementasi (perhatikan saya menggunakan lodash / debounce sebagai pembantu)

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

Berikut adalah contoh TypeScript yang berfungsi untuk mereka yang menggunakan TS dan ingin mendebo asyncfungsi.

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

sedikit terlambat di sini tetapi ini akan membantu. buat kelas ini (ditulis dalam naskah tetapi mudah dikonversi ke 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;
}

dan untuk digunakan

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

0

Anda dapat menggunakan 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

Solusi Julen agak sulit dibaca, inilah kode reaksi yang lebih jelas dan to-the-point untuk siapa saja yang membuatnya tersandung berdasarkan judul dan bukan detail kecil dari pertanyaan.

tl; dr version : saat Anda memperbarui ke pengamat, kirim panggilan metode jadwal dan sebaliknya akan memberi tahu pengamat (atau melakukan ajax, dll.)

Lengkapi jsfiddle dengan contoh komponen 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
Tidakkah ini akan membuat status / waktu debounce global di semua instance InputField, karena itu dibuat dengan definisi kelas? Mungkin itu yang Anda inginkan, tetapi perlu diperhatikan.
robbles

1
berbahaya jika dipasang berulang kali di dom, periksa stackoverflow.com/questions/23123138/…
Sebastien Lorber

2
Ini adalah solusi yang buruk, karena masalah pemasangan ganda - Anda membuat fungsi Anda untuk menjadwalkan perubahan tunggal dan itu bukan ide yang baik. -1
Henrik

0

Hindari penggunaan event.persist()- Anda ingin membiarkan React mendaur ulang acara sintetis. Saya pikir cara terbersih apakah Anda menggunakan kelas atau kait adalah dengan membagi callback menjadi dua bagian:

  1. Panggilan balik tanpa debouncing
  2. Memanggil fungsi yang dilepaskan dengan hanya bagian dari acara yang Anda butuhkan (sehingga acara sintetis dapat didaur ulang)

Kelas

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

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

<div onMouseOver={this.onMouseOver} />

Fungsi

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

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

<div onMouseOver={this.onMouseOver} />

Perhatikan bahwa jika handleMouseOverfungsi Anda menggunakan status dari dalam komponen, Anda harus menggunakan useMemoalih-alih useRefdan meneruskannya sebagai dependensi jika tidak, Anda akan bekerja dengan data basi (tidak berlaku untuk kelas saja).


0

Perpanjang useState hook

import { useState } from "react";
import _ from "underscore"
export const useDebouncedState = (initialState, durationInMs = 500) => {
    const [internalState, setInternalState] = useState(initialState);
    const debouncedFunction = _.debounce(setInternalState, durationInMs);
    return [internalState, debouncedFunction];
};
export default useDebouncedState;

Gunakan kait

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

Ketemu masalah ini hari ini. Memecahkannya menggunakan setTimeoutdan clearTimeout.

Saya akan memberikan contoh bahwa Anda bisa beradaptasi:

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

Anda juga bisa melakukan refactor pada komponen fungsinya sendiri:

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

Dan gunakan seperti:

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.