React.jsでデバウンスを実行する


496

React.jsでデバウンスをどのように実行しますか?

handleOnChangeをデバウンスしたいのですが。

試してみましたdebounce(this.handleOnChange, 200)が動作しません。

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

あなたと同じ問題に遭遇しました。すばらしい答えは以下です!しかし、あなたは間違った方法を使用したと思いますdebounce。ここで、の場合onChange={debounce(this.handleOnChange, 200)}/>debounce function毎回呼び出されます。しかし、実際には、必要なのは、デバウンス関数が返した関数を呼び出すことです。
pingfengafei

回答:


835

2019:フックを試す+デバウンスの約束

これは、私がこの問題を解決する方法の最新バージョンです。私は使うだろう:

これは初期の配線ですが、プリミティブブロックを自分で作成しているので、独自のカスタムフックを作成して、これを1回だけ行う必要があります。

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

そして、あなたはあなたのフックを使うことができます:

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

この例はここで実行さています。詳細については、react-async-hookのドキュメントをお読みください。


2018:デバウンスの約束を試す

API呼び出しをデバウンスして、バックエンドが無駄な要求でいっぱいにならないようにすることがよくあります。

2018年、コールバック(Lodash / Underscore)での作業は気分が悪くなり、エラーが発生しやすくなります。API呼び出しが任意の順序で解決されるため、ボイラープレートや同時実行の問題が発生しやすくなります。

私はあなたの苦痛を解決するためにReactを念頭に置いて小さなライブラリーawesome-debounce-promiseを作成しました

これはそれより複雑であってはなりません:

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

デバウンス機能により、以下が保証されます。

  • API呼び出しはデバウンスされます
  • デバウンスされた関数は常にpromiseを返します
  • 最後の呼び出しで返されたpromiseのみが解決されます
  • this.setState({ result });API呼び出しごとに1 つ発生します

最終的に、コンポーネントがマウント解除された場合は、別のトリックを追加できます。

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

なお、観測(RxJS)も入力をデバウンスにぴったりすることができますが、それは正しく/使用を学ぶために難しいかもしれより強力な抽象化です。


<2017:コールバックデバウンスを引き続き使用しますか?

ここで重要なのは、コンポーネントインスタンスごとに単一のデバウンス(またはスロットル)関数を作成することです。毎回デバウンス(またはスロットル)関数を再作成する必要はありません。また、複数のインスタンスが同じデバウンス関数を共有する必要はありません。

私はこの回答でデバウンス関数を定義していません。これは実際には関連がないためですが、この回答は_.debounce、アンダースコアまたはロダッシュのほか、ユーザーが提供するデバウンス関数でも完全に機能します。


良いアイデア:

デバウンスされた関数はステートフルであるため、コンポーネントインスタンスごとに1つのデバウンスされた関数を作成する必要があります

ES6(クラスプロパティ):推奨

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

ES6(クラスコンストラクター)

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

JsFiddleを参照してください。3つのインスタンスがインスタンスごとに1つのログエントリを生成しています(グローバルに3つになります)。


良い考えではありません:

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

クラス記述オブジェクトの作成中thisは、それ自体が作成されたオブジェクトではないため、機能しません。コンテキストはオブジェクト自体ではないthis.methodため、期待どおりの結果は返されませんthis(実際にはまだ作成されているため、実際にはまだ存在していません)。


良い考えではありません:

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

今回は、を呼び出すデバウンス関数を効果的に作成していますthis.method。問題は、すべてのdebouncedMethod呼び出しでそれを再作成しているため、新しく作成されたデバウンス関数は以前の呼び出しについて何も知らないということです!同じデバウンス機能を長期間再利用する必要があります。そうしないと、デバウンスは発生しません。


良い考えではありません:

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

ここでは少し注意が必要です。

クラスのすべてのマウントされたインスタンスは、同じデバウンス機能を共有しますが、ほとんどの場合、これはあなたが望むものではありません!。JsFiddleを参照してください。3つのインスタンスがグローバルに生成するログエントリは1つだけです。

各コンポーネントインスタンスによって共有されるクラスレベルの単一のデバウンス関数ではなく、各コンポーネントインスタンスのデバウンス関数を作成する必要があります。


Reactのイベントプールを管理する

DOMイベントをデバウンスまたは抑制したい場合が多いため、これは関連しています。

Reactでは、SyntheticEventコールバックで受け取るイベントオブジェクト(つまり、)がプールされます(これは現在、ドキュメント化されています)。つまり、イベントコールバックが呼び出された後、受け取ったSyntheticEventが空の属性でプールに戻され、GC圧力が軽減されます。

したがってSyntheticEvent、元のコールバックと非同期でプロパティにアクセスすると(スロットル/デバウンスの場合など)、アクセスするプロパティが消去される可能性があります。イベントをプールに戻さないようにする場合は、このpersist()メソッドを使用できます。

持続なし(デフォルトの動作:プールされたイベント)

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

hasNativeEvent=falseイベントプロパティがクリーンアップされているため、2番目(非同期)が印刷されます。

持続あり

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

第2回(非同期)が印刷されますhasNativeEvent=trueので、persistあなたがプール内のイベントのバックを入れないようにすることができます。

ここでこれら2つの動作をテストできます:JsFiddle

スロットル/デバウンス機能での使用例については、Julenの回答を読んでくださいpersist()


3
見事な答えは、これは彼らがタイピングを停止した後、数秒間「相互作用」し、フォーム送信したりonBlurイベントでキャンセルすることができるというようフォームフィールドの状態を設定するための素晴らしいです
arush_try.com

8
ES6では、コンストラクター内でメソッドを定義する(奇妙に感じる)代わりhandleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)に、クラスのトップレベルで実行できることに注意してください。インスタンスメンバーを効果的に設定していますが、通常のメソッド定義に少し似ています。constructorまだ定義していない場合は必要ありません。それは主にスタイルの好みだと思います。
thom_nic 2016年

24
componentWillUnmount:でデバウンスされたメソッドをキャンセルすることを忘れないでくださいthis.method.cancel()-そうでなければ、マウントされていないコンポーネントのsetStateが必要になるかもしれません。
elado 2016年

4
@JonasKelloデバウンスされた関数は実際にはステートフルであるため、ステートレスコンポーネント内ではデバウンスできません。そのデバウンスされた関数を保持するにはステートフルコンポーネントが必要ですが、必要に応じて、すでにデバウンスされた関数を使用してステートレスコンポーネントを呼び出すことができます。
Sebastien Lorber

2
すべての回答に関数を書く代わりに_.debounceが含まれているのはなぜですか?その関数にはライブラリ全体が必要ですか?
chifliiiii

217

制御されていないコンポーネント

event.persist()メソッドを使用できます。

アンダースコアを使用した例を次に示します_.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} />
    );
  }

});

編集:このJSFiddleを参照してください


制御されるコンポーネント

更新:上記の例は、制御されていないコンポーネントを示しています。私は常に制御エレメントを使用しているので、上記の別の例を示しますが、event.persist()「トリッキー」を使用しません。

A JSFiddleが利用可能であるとしても。下線なしの例

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

編集:更新された例とJSFiddlesを0.12に対応

編集:セバスチャン・ローバーによって提起された問題に対処するために更新された例

編集:アンダースコアを使用せず、プレーンなJavaScriptデバウンスを使用するjsfiddleで更新。


これは入力に対しては機能しません。デバウンスされた関数のイベントターゲットには値がなくなったため、入力は空のままです。
エータイ2014

1
これは少し複雑です。小道具には少し注意する必要があります。設定した場合<input value={this.props.someprop}...、キーを押して更新してもデバウンスが完了するまでコンポーネントに戻らないため、適切にレンダリングされません。これをvalue=管理対象外にしても問題がない場合はを省略してもかまいませんが、値を事前入力したり、他の場所にバインドしたりする場合は、明らかに機能しません。
Alastair Maw 2014

1
@AlastairMaw質問には制御されていないコンポーネントが含まれていたため、返信にも含まれています。以下に、事前に入力された値を持つ、制御されたコンポーネントの代替バージョンを追加しました。
julen 2014

2
あなたは、DOM内のコンポーネントみとめ時間をマウント見れば、これは非常に危険ですstackoverflow.com/questions/23123138/...
セバスチャン・ローバー

4
これは良い答えですpersistが、などのイベントがたくさんある可能性がある場合は特に使用しないことをお勧めしますmousemove。コードがそのように完全に応答しなくなるのを見てきました。イベント呼び出しでネイティブイベントから必要なデータを抽出し、イベント自体ではなく、データのみでデバウンス/スロットルされた関数を呼び出す方がはるかに効率的です。イベントをそのまま継続する必要はありません
MrE

31

2019: 'useCallback'反応フックを使用する

多くの異なるアプローチを試した後、イベント内useCallbackで使用する複数の呼び出しの問題を解決するには、を使用することが最も簡単で最も効率的であることがわかりました。debounceonChange

Hooks APIドキュメントに従って、

useCallbackは、依存関係の1つが変更された場合にのみ変更されるコールバックの記憶されたバージョンを返します。

空の配列を依存関係として渡すと、コールバックが1回だけ呼び出されるようになります。簡単な実装は次のとおりです。

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

お役に立てれば!


3
フックを使用している場合は、優れたソリューションです。あなたは私をいらいらさせるより多くの時間を節約しました。ありがとう!
カールエドワーズ、

最初に複数の呼び出しが発生する理由を説明していただけますか?debounce()考慮していないonChange()コールバックが同じコールバックメソッドであることを?
El Anonimo

このソリューションを変更して、アプリで機能するようにしました。最初にconst testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);、関数コンポーネントの本体の内側に行を移動する必要がありました。そうしないと、Reactがフックの使用に関するエラーメッセージをその外側に出力します。次に、onChangeイベントハンドラで:<input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}
El Anonimo

これは、このソリューションを使用してユーザーが入力にタイプできるようにし、入力が完了したら、デバウンスされたAPI呼び出しを入力値とともに送信する方法です。stackoverflow.com/questions/59358092/…
El Anonimo

14

私が見つかりました。この記事ジャスティンTulkによっては非常に役立ちます。数回試行した後、react / reduxを使用したより公式な方法であると思われる方法で、Reactの合成イベントプールが原因で失敗したことが示されます。彼のソリューションは、いくつかの内部状態を使用して、入力で変更または入力された値を追跡し、その直後にコールバックsetStateを使用して、リアルタイムで結果を示すスロットル/デバウンスされたreduxアクションを呼び出します。

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

イベントオブジェクトから必要なのがDOM入力要素を取得することだけである場合、解決策ははるかに単純refです。使用するだけです。これにはアンダースコアが必要です:

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は私が欲しいものです!マッハありがとうございます:)
Tazo leladze '19年

14

しばらくテキスト入力に苦労し、自分で完璧な解決策を見つけられなかった後、npmでこれを見つけました:react-debounce-input

以下に簡単な例を示します。

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コンポーネントは、通常の入力要素に割り当てることができるすべての小道具を受け入れます。コードペンで試してみてください

それが他の誰かにも役立ち、時間を節約できることを願っています。


ここにリストされている多くのソリューションを試した後、間違いなく最も簡単でした。
Vadorequest

9

ではdebounce、元の合成イベントをで維持する必要がありますevent.persist()。これはでテストされた実際の例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;

機能コンポーネントを使用すると、これを行うことができます-

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

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


1
私がこれまでに見つけた最高の実装で動作します
Vincent Tang

8

reduxを使用している場合は、ミドルウェアを使用して非常にエレガントな方法でこれを行うことができます。Debounceミドルウェアは次のように定義できます。

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

次に、次のようなアクションクリエイターにデバウンスを追加できます。

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

実際には、npmを使用してこれを実行できるミドルウェアすでに存在します。


applyMiddleware(...)多数ある場合、このミドルウェアは最初にチェーンで実行する必要があると思います
Youssef

タイムアウトは初期化されておらず、最初のclearTimeoutはパラメーターの未定義を処理します。良くない。
Jason Rice

7

ES6クラスとReact 15.xxとlodash.debounceを使用します。ここでは、Reactの参照をここで使用しています。これは、イベントが内部でthisバインドを失うためです。

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

ここにはすでにたくさんの良い情報がありますが、簡潔にする必要があります。これは私にとってはうまくいきます...

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

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

これは私にはうまくいきません。状態は更新されません。私が_debounce ラッパーを削除した場合、それは動作します。私はこのアイデアも大好きです!
Mote Zart

ここで多くのことを提供するにはあなたのコードを見る必要がありますが、何か他のことが起こっているのではないかと思います。stackoverflow.com/questions/23123138/...
スティールチャド

6

Lodash debounce https://lodash.com/docs/4.17.5#debounceメソッドを使用できます。シンプルで効果的です。

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

以下のメソッドを使用して、デバウンスメソッドをキャンセルすることもできます。

this.debounceHandleUpdate.cancel();

お役に立てば幸いです。乾杯!!


5

ご参考までに

次に、別のPoC実装を示します。

  • デバウンス用のライブラリ(例:lodash)なし
  • React Hooks APIの使用

それが役に立てば幸いです:)

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-debounceあなたはReactJSフックで使用できるというパッケージが。

パッケージの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>
  );
}

上記の例からわかるように、変数valueは毎秒1回だけ更新されるように設定されています(1000ミリ秒)。


3

最近の反応とlodashのちょうど別のバリアント。

class Filter extends Component {
  static propTypes = {
    text: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired
  }

  state = {
    initialText: '',
    text: ''
  }

  constructor (props) {
    super(props)

    this.setText = this.setText.bind(this)
    this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
  }

  static getDerivedStateFromProps (nextProps, prevState) {
    const { text } = nextProps

    if (text !== prevState.initialText) {
      return { initialText: text, text }
    }

    return null
  }

  setText (text) {
    this.setState({ text })
    this.onChange(text)
  }

  onChange (text) {
    this.props.onChange(text)
  }

  render () {
    return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
  }
}


3

試しましたか?

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をdebounce()でラップする代わりに、ajax呼び出しをdebounce内のコールバック関数内にラップして、イベントオブジェクトを破棄しないようにしてください。だからこのようなもの:

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

4
イベントオブジェクトは不変ではなく、ReactJSによって破棄されるため、ラップしてクロージャキャプチャを取得しても、コードは失敗します。
Henrik

2

これは私が思いついた例で、別のクラスをデバウンサーでラップしています。これは、デコレータ/高次関数にするのに適しています。

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

2019年後半に、ReactとReact Nativeの別のソリューションがあります。

反応デバウンスコンポーネント

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

それはコンポーネントであり、使いやすく、小さく、広くサポートされています

例:

ここに画像の説明を入力してください

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;

*私はこのコンポーネントの作成者です


1

私は同じ問題への解決策を検索し、このスレッドだけでなく、いくつかの他の人に出会いましたが、彼らは同じ問題を抱えていました:あなたがやろうとしている場合はhandleOnChange機能をして、イベントターゲットから値を必要とし、あなたが得るcannot read property value of nullいくつかのかそのようなエラー。私の場合、私thisはフラクシブルアクションを実行しているため、デバウンスされた関数内のコンテキストを保持する必要もありました。これが私の解決策です。これは私のユースケースでうまく機能するので、誰かがこのスレッドに遭遇した場合に備えて、ここでそのままにしておきます。

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

以下のためthrottleか、debounce最善の方法は、あなたが、たとえば、任意の場所にそれを使用できるように、機能の作成者を作成することです:

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

そしてあなたのrender方法ではあなたがすることができます:

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

このupdateUserProfileFieldメソッドは、呼び出すたびに個別の関数を作成します。

これは動作しません例えば、直接ハンドラを返すようにしようとしないでください。

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

これが機能しない理由は、同じスロットル関数を使用する代わりに、イベントが呼び出されるたびに新しいスロットル関数を生成するため、基本的にスロットルは役に立たないためです;)

また、debounceまたはを使用throttleしない場合、setTimeoutまたは使用しない場合clearTimeout、これが実際に使用される理由です:P


1

これは、関数コンポーネントにラップされた@Abraのアプローチを使用したスニペットです(UIにはファブリックを使用していますが、単純なボタンに置​​き換えるだけです)。

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

私のソリューションはフックベースです(Typescriptで書かれています)。

私は2つの主要なフックを持っているuseDebouncedValueし、useDebouncedCallback

最初 - useDebouncedValue

検索ボックスがあるが、ユーザーが0、5秒の入力をやめた後に、サーバーに検索結果を要求するとします。

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

実装

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

二番目 useDebouncedCallback

コンポーネントのスコープに「デバウンス」関数を作成するだけです。

クリックを止めてから500ms後にアラートを表示するボタンのあるコンポーネントがあるとします。

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

  const debouncedShowAlert = useDebouncedCallback(showAlert, 500);

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

実装(ヘルパーとしてlodash / debounceを使用していることに注意してください)

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

TSを使用していて、async関数をデバウンスしたい人のためのTypeScriptの実用的な例を以下に示します。

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

ここで少し遅れますが、これは役立つはずです。このクラスを作成します(typescriptで記述されていますが、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;
}

そして使用する

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

0

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

ジュレンの解法は読みにくく、質問の細かい詳細ではなく、タイトルに基づいてつまずいた人のための明確で的確な反応コードです。

tl; drバージョン:オブザーバーに更新を送信する場合、代わりにスケジュールメソッドを呼び出し、オブザーバーに通知します(またはajaxなどを実行します)。

サンプルコンポーネントjsfiddleで 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
これは、クラス定義で作成されているため、InputFieldのすべてのインスタンスにわたってデバウンスの状態/タイミングをグローバルにしませんか?多分それがあなたの望みですが、それは関係なく注目に値します。
2014

1
domに複数回マウントすると危険です。stackoverflow.com
questions / 23123138 /…を

2
ダブルマウントの問題のため、これは不適切なソリューションです。scheduleChangeの関数をシングルトンにしていて、それは良い考えではありません。-1
Henrik

0

使用は避けてくださいevent.persist()-Reactに合成イベントをリサイクルさせたい場合。クラスとフックのどちらを使用する場合でも、最もクリーンな方法は、コールバックを2つの部分に分割することです。

  1. デバウンスのないコールバック
  2. 必要なイベントの一部のみを使用して、デバウンスされた関数を呼び出します(したがって、合成イベントをリサイクルできます)

クラス

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

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

<div onMouseOver={this.onMouseOver} />

機能

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

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

<div onMouseOver={this.onMouseOver} />

handleMouseOver関数がコンポーネント内から状態を使用する場合は、useMemo代わりにuseRefを使用して依存関係として渡す必要があることに注意してください。そうでない場合は、古いデータで作業します(もちろんクラスには適用されません)。


0

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;

フックを使用

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

今日この問題に会った。とを使用setTimeoutして解決しましたclearTimeout

あなたが適応できる例を挙げましょう:

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

独自の関数コンポーネントでリファクタリングすることもできます:

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

そしてそれを次のように使用します:

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.