Виконайте дебютування в React.js


496

Як ви проводите дебютування в React.js?

Я хочу денонсувати ручкуOnChange.

Я спробував, 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

Відповіді:


834

2019: спробуйте гачки + обіцяйте дебютацію

Це найсучасніша версія того, як я вирішував би цю проблему. Я б використав:

Це кілька початкових підключень, але ви складаєте примітивні блоки самостійно, і ви можете зробити власний власний гачок, щоб вам це потрібно зробити лише один раз.

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

Це не повинно бути складніше, ніж це:

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 будуть зняті
  • дебютована функція завжди повертає обіцянку
  • вирішиться лише повернута обіцянка останнього дзвінка
  • один this.setState({ result });виклик відбуватиметься за виклик API

Зрештою ви можете додати ще один трюк, якщо ваш компонент відключений:

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

Зауважте, що спостережувані дані (RxJS) також можуть бути чудовим пристосуванням для денонсації входів, але це більш потужна абстракція, яка може бути важче навчитися / використовувати правильно.


<2017: все ще хочете використовувати зворотний дзвінок?

Важливою частиною тут є створення єдиної дебютованої (або приглушеної) функції для кожного компонента . Ви не хочете кожен раз відтворювати функцію debounce (або дросель), і ви не хочете, щоб жоден з декількох екземплярів ділив одну і ту ж функцію, яку деблокували.

Я не визначаю функцію деблокування в цій відповіді, оскільки це не дуже актуально, але ця відповідь буде прекрасно спрацьовувати з _.debounceпідкресленням або подачею, а також з будь-якою функцією розблокування, наданою користувачем.


ГАРНА ІДЕЯ:

Оскільки функції дебюйованості є стаціонарними, ми повинні створити одну дебютовану функцію для кожного компонента .

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контекст не є самим об'єктом (який насправді ще не існує BTW, оскільки він тільки створюється).


НЕ гарна ідея:

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оскільки очищені властивості події.

З наполегливістю

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

Друга (асинхронізація) буде надрукована, hasNativeEvent=trueоскільки persistдозволяє уникнути повернення події в пул.

Ви можете перевірити ці 2 поведінки тут: JsFiddle

Прочитайте відповідь Юлена на прикладі використання persist()з функцією дросельної заслінки / дебютації.


3
Чудова відповідь, це чудово для того, щоб встановити стан форм у формі "взаємодіючих" протягом декількох секунд після того, як вони перестануть друкувати, а потім зможуть скасувати подання форми або onBlur
arush_try.com

8
Зауважте, що в ES6 замість того, щоб визначати свій метод всередині конструктора (здається дивним), ви можете це зробити handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)на найвищому рівні свого класу. Ви все ще ефективно встановлюєте член екземпляра, але це трохи більше нагадує звичайне визначення методу. Немає необхідності в тому constructorвипадку, якщо у вас ще немає визначеного. Я припускаю, що це переважно стиль стилю.
thom_nic

24
Не забудьте скасувати метод, що дебютував, у componentWillUnmount: this.method.cancel()- інакше він може захотіти встановитиState на відключений компонент.
еладо

4
@JonasKello ви не можете дебютувати всередині компонента без стану, оскільки функція дебютованого насправді є справжньою. Вам потрібен компонент stateful, щоб утримувати цю дебютовану функцію, але ви можете викликати компонент без стану, який вже розв'язав функцію, якщо це необхідно.
Себастьян Лорбер

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()"хитрість".

Також доступний 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 до React 0,12

Редагувати: оновлені приклади для вирішення проблеми, порушеного Себастьєном Лорбер

Редагувати: оновлено jsfiddle, що не використовує підкреслення та використовує звичайний javascript-debounce.


Це не працює для входів. Ціль події у функції, що знімається, більше не має значення ... тому вхід залишається порожнім.
Етай

1
Трохи складне, це. Ви повинні бути обережними щодо реквізиту. Якщо ви встановите, <input value={this.props.someprop}...він не відображатиметься належним чином, оскільки оновлення при натисканні клавіші не поверне його до компонента до закінчення дебютування. Добре опустити цей випадок, value=якщо ви щасливі, що це не може бути керованим, але якщо ви хочете попередньо заповнити значення та / або зв’язати його де-небудь ще, очевидно, це не працює.
Alastair Maw

1
@AlastairMaw у запитанні був неконтрольований компонент, тому відповідь має і його. Нижче я додав альтернативну версію для керованих компонентів із попередньо заповненим значенням.
липень

2
це дуже небезпечно, якщо ви монтуєте компоненти часу в DOM, див. stackoverflow.com/questions/23123138/…
Sebastien Lorber

4
Хоча це чудова відповідь, я не рекомендую користуватися persistособливо, коли може бути багато подій, як-от на mousemove. Я бачив, що код стає абсолютно невідповідним таким чином. Набагато ефективніше витягувати потрібні дані з нативного події під час виклику події, а потім викликати функцію відмежування / вимкнення лише з даними, а не саму подію. Не потрібно наполегливо продовжувати подію
MrE

31

2019: Використовуйте реакційний гачок "useCallback"

Спробувавши багато різних підходів, я виявив, що використання useCallbackє найпростішим та найефективнішим у вирішенні проблеми декількох викликів використання debounceв рамках onChangeподії.

Відповідно до документації API Hooks ,

useCallback повертає запам’ятовувану версію зворотного виклику, яка змінюється лише у тому випадку, якщо одна із залежностей змінилася.

Якщо передавати порожній масив як залежність, переконайтеся, що зворотний виклик викликається лише один раз. Ось проста реалізація:

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

Я вважаю цю публікацію Джастіном Тулком дуже корисною. Після декількох спроб, як можна вважати, це більш офіційний спосіб реагування / редукції, він показує, що він провалюється через синтетичне об'єднання подій React . Потім його рішення використовує деякий внутрішній стан для відстеження значення, зміненого / введеного на вході, з правою зворотним викликом, після setStateякого викликає дію скорочення / деблокування, що показує деякі результати в режимі реального часу.

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 - те, що я хочу! Дякую дуже
мачу

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 приймає всі реквізити, які можна призначити звичайному вхідному елементу. Спробуйте це на codepen

Я сподіваюся, що це теж допомагає комусь іншому і економить на них деякий час.


Спробувавши багато перерахованих тут рішень, безумовно, було найпростіше.
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



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 буде мати справу з невизначеним парам. Не добре.
Джейсон Райс

7

Використання ES6 CLASS і React 15.xx & lodash.debounce Im використовуючи реактив рефов тут , оскільки втрати івент це пов'язують внутрішньо.

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

6

Ви можете використовувати метод дебютації Lodash 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

FYI

Ось ще одна реалізація PoC:

  • без бібліотек (наприклад, лодаш) для дебютації
  • за допомогою API React Hooks

Я сподіваюся, що це допомагає :)

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лише раз на секунду (1000 мілісекунд).


3

Ще один варіант з недавньою реакцією та ломашем.

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, тому навіть якщо ви завернете і досягнете захоплення, закриття не вдасться.
Генрік

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 and 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, загорнутий у функціональну складову (ми використовуємо тканину для інтерфейсу користувача, просто замінимо її простою кнопкою)

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

Він просто створює функцію "дебютування" в межах вашого компонента.

Скажімо, у нас є компонент із кнопкою, яка буде показувати сповіщення через 500 мс після того, як ви перестали клацнути по ньому.

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

Ось робочий приклад TypeScript для тих, хто використовує TS і хоче дебютувати asyncфункції.

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

трохи пізно тут, але це повинно допомогти. створити цей клас (його написано в машинописі, але його легко перетворити на 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, оскільки він створений з визначенням класу? Можливо, саме цього ви хочете, але це варто відзначити незалежно.
грабує

1
небезпечно, якщо його встановлюють кілька разів у домі, перевірити stackoverflow.com/questions/23123138/…
Себастьян Лорбер

2
Це неправильне рішення, оскільки проблеми з подвійним монтажем - ви робите свою функцію планувати зміну одиночного, і це не дуже гарна ідея. -1
Генрік

0

Уникайте використання event.persist()- ви хочете дозволити React переробляти синтетичну подію. Я вважаю, що найчистіший спосіб використання класів або гаків - це розділення зворотного дзвінка на дві частини:

  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

Розширення використання Державний гачок

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.