在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

Answers:


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年:尝试Promise反弹

我们通常希望对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调用将被删除
  • 去抖功能总是返回一个承诺
  • 只有最后一次通话的返回承诺会解决
  • 一个this.setState({ result });将每个API调用发生

最终,如果卸载了组件,您可能会添加另一个技巧:

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

请注意,Observables(RxJS)也可以非常适合去抖动输入,但是它是更强大的抽象,可能很难正确学习/使用。


<2017:仍要使用回调反跳吗?

这里的重要部分是为每个组件实例创建一个单独的去抖动(或抑制)功能。您不想每次都重新创建去抖动(或调节)功能,也不想多个实例共享相同的去抖动功能。

我没有在此答案中定义反跳功能,因为它实际上并不相关,但是该答案与_.debounce下划线或lodash以及任何用户提供的反跳功能完美配合。


好主意:

因为去抖功能是有状态的,所以我们必须为每个组件实例创建一个去抖功能

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因为事件属性已被清除。

与坚持

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

第二(异步)将打印,hasNativeEvent=true因为persist您可以避免将事件放回池中。

您可以在此处测试这两种行为: JsFiddle

阅读Julen的答案,以了解如何使用persist()油门/反跳功能。


3
绝佳的答案,这非常适合在表单域停止输入后将表单域状态设置为“交互”几秒钟,然后可以在表单提交或onBlur上取消
arush_try.com 15-10-29

8
请注意,在ES6中,您可以handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)在类的顶层执行操作,而不是在构造函数中定义方法(感觉很奇怪)。您仍在有效地设置实例成员,但它看起来有点像普通的方法定义。无需一个constructor,如果你不已经有一个定义。我想这主要是样式偏好。
thom_nic

24
别忘了在中取消取消反跳方法componentWillUnmountthis.method.cancel()-否则可能要在未安装的组件上设置setState。
elado '16

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()“特技”。

也可以使用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

编辑:更新了示例以解决Sebastien Lorber提出的问题

编辑:使用不使用下划线并使用普通JavaScript反跳的jsfiddle更新。


这不适用于输入。防反跳功能中的事件目标不再具有值...,因此输入保持为空。
Etai 2014年

1
这个有点复杂。您必须谨慎对待道具。如果设置,<input value={this.props.someprop}...则它将无法正确呈现,因为按键的更新要等到反跳之后才能恢复到组件中。value=如果您希望对此值不受管理,可以忽略它,但是如果您想预先填充值和/或将其绑定到其他位置,则显然这是行不通的。
Alastair Maw 2014年

1
@AlastairMaw这个问题包含一个不受控制的组件,这就是为什么回复中也包含该组件。我在下面添加了一个受控组件的替代版本,并带有一个预先填充的值。
2014年

2
如果你安装在DOM组件多发时期,看到这是非常危险的stackoverflow.com/questions/23123138/...
塞巴斯蒂安·洛伯

4
虽然这是一个很好的答案,但我不建议您persist特别在可能发生许多事件(例如on)的情况下使用mousemove。我已经看到代码以这种方式变得完全无响应。从事件调用中的本机事件中提取所需的数据,然后仅使用数据(而不是事件本身)来调用去反跳/节流函数,效率更高。无需以这种方式坚持举办活动
MrE18

31

2019:使用'useCallback'react钩子

尝试许多不同的方法后,我发现使用useCallback是最简单和解决在使用的多个呼叫问题最有效debounce的范围内onChange活动。

根据Hooks API文档

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 '19

我修改了此解决方案以使其在我的应用中正常工作。首先,我必须将行移动到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 '19

14

我发现Justin Tulk的这篇文章很有帮助。经过几次尝试,以一种被认为是re​​act / 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。请注意,这需要Underscore

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

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


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将处理未定义的参数。不好。
詹森·赖斯

7

使用ES6 CLASS和React 15.xx&lodash.debounce Im 在这里使用React的引用,因为事件在内部丢失了此绑定。

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防反跳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挂钩一起使用的包。

从软件包的自述文件中:

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

只是最近反应和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破坏了,所以即使您包装并获得了闭包捕获,代码也将失败。
亨里克

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

用于throttledebounce最好的方法是创建一个函数的创造者,因此您可以任意使用它在哪里,例如:

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

之所以不起作用的原因是因为每次调用该事件都会生成一个新的节气门功能,而不是使用相同的节气门功能,所以基本上节气门将是无用的;)

另外,如果您使用debouncethrottle不需要setTimeoutclearTimeout,这实际上就是我们使用它们的原因:P


1

这是一个使用@Abra方法包装在功能组件中的代码片段(我们在UI中使用fabric,只需将其替换为简单的按钮即可)

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个主钩子 useDebouncedValueuseDebouncedCallback

第一- 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

这里有点晚,但这应该有所帮助。创建此类(以打字稿编写,但易于将其转换为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

Julen解决方案有点难以阅读,对于那些根据标题而不是问题的微小细节绊倒他的人,这里提供了更清晰,更准确的反应代码。

tl; dr版本:当您要更新给观察者时,请发送调用schedule方法,而该方法实际上会通知观察者(或执行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
由于存在双重安装问题,因此这是一个糟糕的解决方案-您正在使函数能够安排更改一个单例,这不是一个好主意。-1
Henrik

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

扩展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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.