ChatGPT解决这个技术问题 Extra ChatGPT

如何进行去抖动?

你如何在 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。但是,实际上,我们需要的是调用 debounce 函数返回的函数。

S
Sebastien Lorber

2019:尝试钩子+承诺去抖动

这是我如何解决这个问题的最新版本。我会使用:

awesome-debounce-promise 去抖动异步函数

使用常量将去抖函数存储到组件中

react-async-hook 将结果放入我的组件中

这是一些初始接线,但您正在自己组合原始块,您可以制作自己的自定义钩子,这样您只需要这样做一次。

// 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 调用将被去抖动

debounced 函数总是返回一个 promise

只有最后一个调用返回的承诺会解决

单个 this.setState({ result });每次 API 调用都会发生

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

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

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

< 2017: 还想使用回调去抖动?

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

我没有在此答案中定义去抖功能,因为它并不真正相关,但此答案将与下划线或 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 调用时都重新创建它,因此新创建的 debounce 函数对以前的调用一无所知!您必须随着时间的推移重复使用相同的去抖动功能,否则不会发生去抖动。

不是一个好主意:

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) => { /* onChange handler code here */ }, timeout),而不是在构造函数中定义您的方法(感觉很奇怪)。您仍然有效地设置了一个实例成员,但它看起来更像是一个普通的方法定义。如果您还没有定义 constructor,则不需要 constructor。我想这主要是一种风格偏好。
不要忘记取消 componentWillUnmount: this.method.cancel() 中的 debounced 方法 - 否则它可能想要在未安装的组件上设置 setState。
@JonasKello 你不能在无状态组件内去抖动,因为去抖动函数实际上是有状态的。您需要一个有状态的组件来保存该去抖函数,但如果需要,您可以调用具有已经去抖函数的无状态组件。
为什么所有答案都包括 _.debounce 而不是编写函数?它需要整个库来实现该功能吗?
O
Oliver Watkins

不受控制的组件

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

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) => 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);}}
以下是我如何使用此解决方案让用户输入输入,然后在他完成输入后发送带有输入值的去抖动 API 调用。 stackoverflow.com/questions/59358092/…
添加到上面的答案---- const someFunction = (text) => { dispatch({ type: "addText", payload: { id, text, }, }); }; handler(e.target.value)} />
a
andrewdotnich

在与文本输入斗争了一段时间后,我自己没有找到完美的解决方案,我在 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,因为这个原因几乎没用)
s
sledgeweight

我发现 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}
        />
    )
  }
}

状态组件的不错解决方案。
M
Mohan Dere

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


最后是一个使用类组件的简短、一致但完整的示例。谢谢!
I
Ian Kemp

如果您需要从事件对象中获取 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 是我想要的!非常感谢你 :)
A
Adam Pietrasiak

我的解决方案是基于钩子的(用 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

它只是在您的组件范围内创建一个“去抖动”功能。

假设我们有一个带有按钮的组件,该按钮会在您停止单击后 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;
}

喜欢这个解决方案,因为它不需要新的依赖项
d
dipenparmar12

可以有一个使用反应钩子的简单方法。

第 1 步:定义一个状态来维护搜索到的文本

const [searchTerm, setSearchTerm] = useState('')

第 2 步:使用 useEffect 捕捉搜索词的任何变化

useEffect(() => {
  const delayDebounceFn = setTimeout(() => {
    if (searchTerm) {
      // write your logic here
    }
  }, 400)

  return () => clearTimeout(delayDebounceFn)
}, [searchTerm])

第 3 步:编写一个函数来处理输入变化

function handleInputChange(value) {
  if (value) {
    setSearchTerm(value)
  }
}

就这样 !在需要时调用此方法


如果卸载了用户组件,如何取消它?
A
Art

您可以将 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]);
c
chad steele

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

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/…
对我来说就像一个魅力。如上所述包装绑定的处理函数,然后根据字段输入更新处理函数中的状态。谢谢!
M
Matt

如果您使用的是 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 将处理未定义的参数。不好。
S
STEEL

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

类 UserInput 扩展 React.Component { 构造函数(props) { super(props); this.state = {用户输入:“”}; this.updateInput = _.debounce(this.updateInput, 500); } updateInput(userInput) { this.setState({ userInput }); //OrderActions.updateValue(userInput);//做一些服务器的事情 } render() { return (

User typed: { this.state.userInput }

this.updateInput(this.refs.userValue.value) } type = "text" />
); } } ReactDOM.render( < UserInput /> , document.getElementById('root') );


D
Dinesh Madanlal

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

希望它可以帮助你。干杯!!


k
kenju

供参考

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

R
Rebs

2019 年末,React 和 React Native 有了另一种解决方案:

react-debounce-component

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

它是一个组件,易于使用,小巧且支持广泛

例子:

https://i.stack.imgur.com/ptVlA.gif

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;

*我是这个组件的创建者


X
Xinan

我在这个问题下找不到任何答案,提到我正在使用的方法,所以只想在这里提供一个我认为最适合我的用例的替代解决方案。

如果您正在使用名为 react-use 的流行的 react hooks 工具包库,那么有一个名为 useDebounce() 的实用程序钩子以一种非常优雅的方式实现了谴责逻辑。

const [query, setQuery] = useState('');

useDebounce(
  () => {
    emitYourOnDebouncedSearchEvent(query);
  },
  2000,
  [query]
);

return <input onChange={({ currentTarget }) => setQuery(currentTarget.value)} />

有关详细信息,请直接查看 lib 的 github 页面。

https://github.com/streamich/react-use/blob/master/docs/useDebounce.md


O
Or Assayag

至于 2021 年 6 月,您可以简单地实施 xnimorz 解决方案:https://github.com/xnimorz/use-debounce

import { useState, useEffect, useRef } from "react";
// Usage
function App() {
  // State and setters for ...
  // Search term
  const [searchTerm, setSearchTerm] = useState("");
  // API search results
  const [results, setResults] = useState([]);
  // Searching status (whether there is pending API request)
  const [isSearching, setIsSearching] = useState(false);
  // Debounce search term so that it only gives us latest value ...
  // ... if searchTerm has not been updated within last 500ms.
  // The goal is to only have the API call fire when user stops typing ...
  // ... so that we aren't hitting our API rapidly.
  const debouncedSearchTerm = useDebounce(searchTerm, 500);
  // Effect for API call
  useEffect(
    () => {
      if (debouncedSearchTerm) {
        setIsSearching(true);
        searchCharacters(debouncedSearchTerm).then((results) => {
          setIsSearching(false);
          setResults(results);
        });
      } else {
        setResults([]);
        setIsSearching(false);
      }
    },
    [debouncedSearchTerm] // Only call effect if debounced search term changes
  );
  return (
    <div>
      <input
        placeholder="Search Marvel Comics"
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {isSearching && <div>Searching ...</div>}
      {results.map((result) => (
        <div key={result.id}>
          <h4>{result.title}</h4>
          <img
            src={`${result.thumbnail.path}/portrait_incredible.${result.thumbnail.extension}`}
          />
        </div>
      ))}
    </div>
  );
}
// API search function
function searchCharacters(search) {
  const apiKey = "f9dfb1e8d466d36c27850bedd2047687";
  return fetch(
    `https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`,
    {
      method: "GET",
    }
  )
    .then((r) => r.json())
    .then((r) => r.data.results)
    .catch((error) => {
      console.error(error);
      return [];
    });
}
// Hook
function useDebounce(value, delay) {
  // State and setters for debounced value
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(
    () => {
      // Update debounced value after delay
      const handler = setTimeout(() => {
        setDebouncedValue(value);
      }, delay);
      // Cancel the timeout if value changes (also on delay change or unmount)
      // This is how we prevent debounced value from updating if value is changed ...
      // .. within the delay period. Timeout gets cleared and restarted.
      return () => {
        clearTimeout(handler);
      };
    },
    [value, delay] // Only re-call effect if value or delay changes
  );
  return debouncedValue;
}

S
Silicum Silium

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

Debouncing with React Hooks

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


s
superluminary

2022 - 使用 useEffect 挂钩

此时最好的选择是使用 useEffect 挂钩。 useEffect 允许您设置一个函数,该函数可以修改状态以响应某些异步事件。去抖动是异步的,因此 useEffect 可以很好地用于此目的。

如果你从钩子返回一个函数,返回的函数将在再次调用钩子之前被调用。这使您可以取消先前的超时,从而有效地消除功能。

例子

这里我们有两个状态,valuetempValue。设置 tempValue 将触发 useEffect 挂钩,该挂钩将启动 1000 毫秒超时,这将调用函数将 tempValue 复制到 value

该钩子返回一个取消设置计时器的函数。当再次调用钩子(即按下另一个键)时,超时被取消并重置。

const DebounceDemo = () => {
  const [value, setValue] = useState();
  const [tempValue, setTempValue] = useState();

  // This hook will set a 1000 ms timer to copy tempValue into value
  // If the hook is called again, the timer will be cancelled
  // This creates a debounce
  useEffect(
    () => {
      // Wait 1000ms before copying the value of tempValue into value;
      const timeout = setTimeout(() => {
        setValue(tempValue);
      }, 1000);

      // If the hook is called again, cancel the previous timeout
      // This creates a debounce instead of a delay
      return () => clearTimeout(timeout);
    },
    // Run the hook every time the user makes a keystroke
    [tempValue]
  )

  // Here we create an input to set tempValue. 
  // value will be updated 1000 ms after the hook is last called, 
  // i.e after the last user keystroke.
  return (
    <>
      <input 
        onChange={
          ({ target }) => setTempValue(target.value)
        }
      />
      <p>{ value }</p>
    </>
  )
}

感谢您的出色回答。有小错字 setValue(tempvalue) -> setValue(tempValue)
cancelTimeout 应该是 clearTimeout
@Cafn - 更新。感谢您发现
p
puchu

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

Z
Zhivko Zhelev

你试过了吗?

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

这与原始海报的解决方案有什么不同吗?
是的,这里不同:debounce(handleChange, 200);
R
Robert

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

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

因为事件对象不是不可变的并且被 ReactJS 销毁,所以即使你包装并获得闭包捕获,代码也会失败。
m
mlucool

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

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

T
Thread Pitt

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

import React, { useCallback } from "react";
import { debounce } from "lodash";

import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';

const debounceTimeInMS = 2000;

export const PrimaryButtonDebounced = (props) => {

    const debouncedOnClick = debounce(props.onClick, debounceTimeInMS, { leading: true });

    const clickHandlerDebounced = useCallback((e, value) => {

        debouncedOnClick(e, value);

    },[]);

    const onClick = (e, value) => {

        clickHandlerDebounced(e, value);
    };

    return (
        <PrimaryButton {...props}
            onClick={onClick}
        />
    );
}

F
Francisco Hanna

今天遇到了这个问题。使用 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

u
user1079877

这个解决方案不需要任何额外的库,当用户按下回车键时它也会启动:

const debounce = (fn, delay) => {
    let timer = null;
    return function() {
        const context = this,
        args = arguments;
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(context, args);
        }, delay);
    };  
}

const [search, setSearch] = useState('');
const [searchFor, setSearchFor] = useState(search);

useEffect(() => {
    console.log("Search:", searchFor);
}, [searchFor]);

const fireChange = event => {
    const { keyCode } = event;
    if (keyCode === 13) {
        event.preventDefault();
        setSearchFor(search);
    }
}

const changeSearch = event => {
    const { value } = event.target;
    setSearch(value);
    debounceSetSearchFor(value);
};

const debounceSetSearchFor = useCallback(debounce(function(value) {
    setSearchFor(value);
}, 250), []);

输入可能是:

<input value={search} onKeyDown={fireChange} onChange={changeSearch}  />

Pureeeeeeeee JS,喜欢它
G
Gerson Diniz

钩:

import {useState} from "react";

const useDebounce = ({defaultTimeout = 250, defaultIdentifier = 'default'} = {}) => {

    const [identifiers, setIdentifiers] = useState({[defaultIdentifier]: null});

    return ({fn = null, identifier = defaultIdentifier, timeout = defaultTimeout} = {}) => {
        if (identifiers.hasOwnProperty(identifier)) clearTimeout(identifiers[identifier]);
        setIdentifiers({...identifiers, [identifier]: setTimeout(fn, timeout)});
    };
};

export default useDebounce;

并在任何地方使用它(在同一个文件中使用标识符来防止并发),例如:

const debounce = useDebounce();

const handlerA = () => {
    debounce({fn: () => console.log('after 2000ms of last call with identifier A'), identifier: 'A', timeout: 2000});
};

const handlerB = () => {
    debounce({fn: () => console.log('after 1500ms of last call with identifier B'), identifier: 'B', timeout: 1500});
};

看起来不错,如果我理解正确,一个用例可能类似于以下代码段: const debounce = useDebounce(); const debouncedSearchInputHandler = (event) => { setSearchInput(event.target.value); debounce({fn: () => startRestCall(event.target.value), timeout: 1000}); };
S
Siva Kannan

简单有效
https://www.npmjs.com/package/use-debounce
use-debounce

import { useDebouncedCallback } from 'use-debounce';

function Input({ defaultValue }) {
  const [value, setValue] = useState(defaultValue);
  const debounced = useDebouncedCallback(
    (value) => {
      setValue(value);
    },
    // delay
    1000
  );

  return (
    <div>
      <input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />
      <p>Debounced value: {value}</p>
    </div>
  );
}

请为不赞成票添加评论,我一直在我当前的应用程序中使用此代码,并且它运行良好
这只会发送输入的最后一个字母
E
Edward

我正在寻找相同问题的解决方案,并且遇到了这个线程以及其他一些线程,但他们遇到了同样的问题:如果您尝试执行 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)