【问题标题】:Perform debounce in React.js在 React.js 中执行去抖动
【发布时间】:2021-01-26 10:10:02
【问题描述】:

如何在 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)}/&gt;时,每次都会调用debounce function。但实际上,我们需要的是调用 debounce 函数返回的函数。

标签: javascript reactjs


【解决方案1】:

与其将handleOnChange 包装在debounce() 中,不如将ajax 调用包装在debounce 内部的回调函数中,从而不会破坏事件对象。所以是这样的:

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

【讨论】:

  • 因为事件对象不是不可变的并且被 ReactJS 销毁,所以即使你包装并获得闭包捕获,代码也会失败。
【解决方案2】:

不受控制的组件

您可以使用event.persist() method

下面是一个使用下划线_.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} />
    );
  }

});

编辑:见this JSFiddle


受控组件

更新:上面的例子显示了一个uncontrolled component。我一直使用受控元素,所以这是上述的另一个例子,但没有使用event.persist()“诡计”。

JSFiddle is available 也是如此。 Example without underscore

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 debounce 的 jsfiddle 更新。

【讨论】:

  • 这不适用于输入。 debounced 函数中的事件目标不再具有值...因此输入保持为空。
  • 有点复杂,这个。你必须对道具有点小心。如果您设置&lt;input value={this.props.someprop}...,那么它将无法正确呈现,因为按键上的更新直到去抖动之后才会返回到组件中。如果您对它不受管理感到高兴,可以省略 value=,但如果您想预先填充该值和/或将其绑定到其他地方,那么显然这不起作用。
  • @AlastairMaw 这个问题有一个不受控制的部分,这就是为什么回复也有它的原因。我在下面添加了受控组件的替代版本,具有预填充的值。
  • 如果在 DOM 中多次挂载组件,这是非常危险的,请参阅stackoverflow.com/questions/23123138/…
  • 虽然这是一个很好的答案,但我不建议使用persist,尤其是在可能有很多事件的情况下,比如mousemove。我已经看到代码以这种方式变得完全没有响应。在事件调用中从本机事件中提取所需数据,然后仅使用数据而不是事件本身调用去抖动/节流函数会更有效。无需以这种方式持久化事件
【解决方案3】:

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

});

【讨论】:

  • 这不会使所有 InputField 实例的去抖动状态/时间成为全局,因为它是使用类定义创建的吗?也许这就是你想要的,但无论如何都值得注意。
  • 如果在 dom 中多次挂载会很危险,请查看stackoverflow.com/questions/23123138/…
  • 这是一个糟糕的解决方案,因为双挂载问题——你正在让你的函数来 scheduleChange 一个单例,这不是一个好主意。 -1
【解决方案4】:

您也可以使用自己编写的 mixin,如下所示:

var DebounceMixin = {
  debounce: function(func, time, immediate) {
    var timeout = this.debouncedTimeout;
    if (!timeout) {
      if (immediate) func();
      this.debouncedTimeout = setTimeout(function() {
        if (!immediate) func();
        this.debouncedTimeout = void 0;
      }.bind(this), time);
    }
  }
};

然后像这样在你的组件中使用它:

var MyComponent = React.createClass({
  mixins: [DebounceMixin],
  handleClick: function(e) {
    this.debounce(function() {
      this.setState({
        buttonClicked: true
      });
    }.bind(this), 500, true);
  },
  render: function() {
    return (
      <button onClick={this.handleClick}></button>
    );
  }
});

【讨论】:

  • 这不是去抖动,而是“延迟”。 Debounce 重置超时之前发生的每个事件。 -1
  • @Henrik 我的错,你是对的。顺便说一句,像这样去抖动很容易。
  • 这个解决方案没有回答这个问题,因为它会在指定的超时之后触发操作。但在本主题中,如果在超时内多次调用 debounce,则超时应该是“可扩展的”。
【解决方案5】:

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

您会发现此示例运行here,您应该阅读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 才会解决
  • 每个 API 调用都会发生一个 this.setState({ result });

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

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

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


这里的重要部分是为每个组件实例创建一个去抖动(或节流)函数。您不希望每次都重新创建去抖(或限制)函数,也不希望多个实例共享相同的去抖函数。

我没有在此答案中定义去抖功能,因为它并不真正相关,但此答案将与下划线或 lodash 的 _.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 上下文不是对象本身(实际上它实际上并不存在,顺便说一句,因为它刚刚被创建)。


不是一个好主意:

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)会被汇集(现在是 documented)。这意味着在事件回调被调用后,你收到的 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's answer 了解使用persist() 和节流/去抖动功能的示例。

【讨论】:

  • 绝妙的答案,这非常适合在表单字段停止输入后将状态设置为“交互”几秒钟,然后能够在表单提交或 onBlur 上取消
  • 请注意,在 ES6 中,您可以在类的顶层执行 handleOnChange = debounce((e) =&gt; { /* onChange handler code here */ }, timeout) ,而不是在构造函数中定义您的方法(感觉很奇怪)。您仍然有效地设置了一个实例成员,但它看起来更像是一个普通的方法定义。如果您还没有定义constructor,则不需要。我想这主要是一种风格偏好。
  • 别忘了取消componentWillUnmount: this.method.cancel() 中的去抖动方法 - 否则它可能想要在未安装的组件上设置状态。
  • @JonasKello 你不能在无状态组件内去抖动,因为去抖动函数实际上是有状态的。您需要一个有状态组件来保存该去抖函数,但如果需要,您可以调用具有已去抖函数的无状态组件。
  • 为什么所有答案都包含 _.debounce 而不是编写函数?该功能需要整个库吗?
【解决方案6】:

我正在寻找相同问题的解决方案,遇到了这个线程以及其他一些线程,但他们遇到了同样的问题:如果您尝试执行 handleOnChange 函数并且您需要来自事件目标的值, 你会得到cannot read property value of null 或一些这样的错误。就我而言,我还需要在 debounced 函数中保留 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)

【讨论】:

    【解决方案7】:

    这是我想出的一个例子,它用去抖动器包装了另一个类。这很适合被制作成装饰器/高阶函数:

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

    【讨论】:

      【解决方案8】:

      如果您只需要从事件对象中获取 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} />
          }
      }
      

      【讨论】:

      • defaultValue 是我想要的!非常感谢你:)
      【解决方案9】:

      如果您使用的是 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}
      }
      

      实际上有 already middleware 你可以下 npm 为你做这件事。

      【讨论】:

      • 如果我们有很多,我认为这个中间件必须是第一个在applyMiddleware(...)链中执行的中间件
      • 超时未初始化,第一个 clearTimeout 将处理未定义的参数。不好。
      【解决方案10】:

      对于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

      【讨论】:

        【解决方案11】:

        我发现 Justin Tulk 的 this post 非常有帮助。经过几次尝试,人们认为这是更正式的 react/redux 方式,它表明它由于React's synthetic event pooling 而失败。然后,他的解决方案使用一些内部状态来跟踪输入中更改/输入的值,并在 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}
                />
            )
          }
        }
        

        【讨论】:

        • 状态组件的好解决方案。
        【解决方案12】:

        在与文本输入斗争了一段时间后,我自己没有找到完美的解决方案,我在 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

        我希望它也可以帮助其他人并节省他们一些时间。

        【讨论】:

        • 在尝试了这里列出的许多解决方案之后,绝对是最简单的。
        • 这确实是更好的解决方案!不仅因为它使用最少的代码,它还允许对类函数进行去抖动(不像 awesome-debounce-promise,因为这个原因几乎没用)
        【解决方案13】:

        使用 ES6 CLASS 和 React 15.x.x 和 lodash.debounce 我在这里使用 React 的 refs,因为事件在内部丢失了 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>

        【讨论】:

          【解决方案14】:

          您可以使用 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();
          

          希望对您有所帮助。干杯!!

          【讨论】:

            【解决方案15】:

            这里已经有很多很好的信息,但要简洁。这对我有用...

            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 包装器,它就可以工作。不过我喜欢这个主意!
            • 我必须看到你的代码才能在这里提供很多东西,但我怀疑还有其他事情发生......希望这个更彻底的答案会有所启发。 stackoverflow.com/questions/23123138/…
            • 对我来说就像一个魅力。如上所述包装绑定的处理函数,然后根据字段输入更新处理函数中的状态。谢谢!
            【解决方案16】:

            只是最近的 react 和 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)} />)
              }
            }
            

            【讨论】:

              【解决方案17】:

              你可以使用tlencetlence

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

              【讨论】:

                【解决方案18】:

                对于那些使用 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)
                     });
                 }
                

                【讨论】:

                  【解决方案19】:

                  仅供参考

                  这是另一个 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}
                      />
                    );
                  }
                  

                  【讨论】:

                    【解决方案20】:

                    使用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

                    【讨论】:

                      【解决方案21】:

                      这里有点晚了,但这应该会有所帮助。 创建这个类(它是用打字稿写的,但很容易转换成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);
                      

                      【讨论】:

                        【解决方案22】:

                        一个不错且干净的解决方案,不需要任何外部依赖:

                        Debouncing with React Hooks

                        它使用自定义加上 useEffect React 钩子和 setTimeout / clearTimeout 方法。

                        【讨论】:

                          【解决方案23】:

                          避免使用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 并将它们作为依赖项传递,否则您将使用陈旧数据(当然不适用于类)。

                          【讨论】:

                            【解决方案24】:

                            有一个 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 毫秒)。

                            【讨论】:

                            • 2021年1月依然是最佳选择
                            • 所以如果我想在每次设置值时触发一个事件,我会这样做吗? - useEffect(() => { // 这里的函数 }, [value]);
                            【解决方案25】:

                            2019:使用“useCallback”反应钩子

                            在尝试了许多不同的方法后,我发现使用useCallback 是解决在onChange 事件中使用debounce 的多次调用问题最简单和最有效的方法。

                            根据Hooks API documentation

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

                            希望这会有所帮助!

                            【讨论】:

                            • 如果您使用钩子,这是一个很好的解决方案。你为我节省了更多时间的挫败感。谢谢!
                            • 您能否首先解释一下为什么会发生多次调用? debounce() 不认为onChange() 回调是同一个回调方法吗?
                            • 我修改了这个解决方案,让它在我的应用程序中工作。首先,我必须将行 const testFunc2 = useCallback(debounce((text) =&gt; console.log('testFunc2() has ran:', text), 1000) , []); 移动到函数组件的主体内,否则 React 会输出一条关于在其外部使用钩子的错误消息。然后在onChange 事件处理程序中:&lt;input type='text' name='name' className='th-input-container__input' onChange={evt =&gt; {testFunc2(evt.target.value);}}
                            • 以下是我如何使用此解决方案让用户输入输入,然后在他完成输入后发送带有输入值的去抖动 API 调用。 stackoverflow.com/questions/59358092/….
                            • 添加到上面的答案---- const someFunction = (text) => { dispatch({ type: "addText", payload: { id, text, }, }); }; handler(e.target.value)} />
                            【解决方案26】:

                            现在 late/2019 有另一种 React 和 React Native 解决方案:

                            react-debounce-component

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

                            *我是这个组件的创建者

                            【讨论】:

                              【解决方案27】:

                              这是一个使用 @Abra 方法的 sn-p 封装在一个函数组件中 (我们为 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}
                                      />
                                  );
                              }
                              

                              【讨论】:

                                【解决方案28】:

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

                                【讨论】:

                                  【解决方案29】:

                                  今天遇到了这个问题。使用setTimeoutclearTimeout 解决了这个问题。

                                  我将举一个你可以适应的例子:

                                  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
                                  

                                  【讨论】:

                                    【解决方案30】:

                                    你试过了吗?

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

                                    【讨论】:

                                    • 这与原始发帖人的解决方案有什么不同吗?
                                    猜你喜欢
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2019-03-01
                                    • 1970-01-01
                                    • 2019-03-15
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多