关于javascript:在React.js中执行debounce

Perform debounce in React.js

你如何在React.js中进行去抖动?

我想辩论handleOnChange。

我尝试使用debounce(this.handleOnChange, 200),但它不起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
  }
});


2019:尝试钩子+承诺去抖

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

  • 真棒 - 去抖 - 承诺去抖异步功能
  • use-constant将去抖功能存储到组件中
  • react-async-hook将结果输入到我的组件中
  • 这是一些初始接线,但您自己组成原始块,并且您可以创建自己的自定义挂钩,这样您只需要执行一次。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    const useSearchStarwarsHero = () => {
      // Handle the input text state
      const [inputText, setInputText] = useState('');

      // Debounce the original search async function
      const debouncedSearchStarwarsHero = useConstant(() =>
        AwesomeDebouncePromise(searchStarwarsHero, 300)
      );

      const search = useAsync(
        async text => {
          if (text.length === 0) {
            return [];
          } else {
            return debouncedSearchStarwarsHero(text);
          }
        },
        // Ensure a new request is made everytime the text changes (even if it's debounced)
        [inputText]
      );

      // Return everything needed for the hook consumer
      return {
        inputText,
        setInputText,
        search,
      };
    };

    然后你可以使用你的钩子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    const SearchStarwarsHeroExample = () => {
      const { inputText, setInputText, search } = useSearchStarwarsHero();
      return (
       
          <input value={inputText} onChange={e => setInputText(e.target.value)} />
         
            {search.loading && ...}
            {search.error && Error: {search.error.message}}
            {search.result && (
             
                Results: {search.result.length}
                <p>

                  {search.result.map(hero => (
                    <li key={hero.name}>{hero.name}
    </li>

                  ))}
               
    </p>Ok.


             
            )}
         
       
      );
    };

    您将在此处找到此示例,您应该阅读react-async-hook文档以获取更多详细信息。

    2018年:尝试承诺去债

    我们经常要去除API调用以避免使用无用的请求充斥后端。

    在2018年,使用回调(Lodash / Underscore)感觉不好并且容易出错。由于API调用以任意顺序解析,因此很容易遇到样板和并发问题。

    我已经创建了一个带React的小库来解决你的痛苦:真棒 - 去抖 - 承诺。

    这应该不是那么复杂:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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
  • 只有最后一个电话的退回承诺才能解决
  • 每次API调用都会发生一次this.setState({ result });
  • 最后,如果您的组件卸载,您可以添加另一个技巧:

    1
    2
    3
    componentWillUnmount() {
      this.setState = () => {};
    }

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

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

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

    我没有在这个答案中定义去抖函数,因为它不是真正相关的,但是这个答案对于_.debounce的下划线或lodash以及任何用户提供的去抖函数都可以完美地运行。

    好主意:

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

    ES6(类属性):推荐

    1
    2
    3
    4
    5
    class SearchBox extends React.Component {
        method = debounce(() => {
          ...
        });
    }

    ES6(类构造函数)

    1
    2
    3
    4
    5
    6
    7
    class SearchBox extends React.Component {
        constructor(props) {
            super(props);
            this.method = debounce(this.method.bind(this),1000);
        }
        method() { ... }
    }

    ES5

    1
    2
    3
    4
    5
    6
    var SearchBox = React.createClass({
        method: function() {...},
        componentWillMount: function() {
           this.method = debounce(this.method.bind(this),100);
        },
    });

    请参阅JsFiddle:3个实例每个实例生成1个日志条目(全局生成3个)。

    不是一个好主意:

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

    它不起作用,因为在类描述对象创建期间,this不是自己创建的对象。 this.method不会返回您期望的内容,因为this上下文不是对象本身(实际上它实际上并不存在BTW,因为它刚刚被创建)。

    不是一个好主意:

    1
    2
    3
    4
    5
    6
    7
    var SearchBox = React.createClass({
      method: function() {...},
      debouncedMethod: function() {
          var debounced = debounce(this.method,100);
          debounced();
      },
    });

    这次你有效地创建了一个调用this.method的去抖动函数。问题是你在每次debouncedMethod调用时重新创建它,所以新创建的debounce函数对以前的调用一无所知!您必须重复使用相同的去抖功能,否则不会发生去抖动。

    不是一个好主意:

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

    这在这里有点棘手。

    该类的所有已安装实例将共享相同的去抖功能,并且通常这不是您想要的!请参阅JsFiddle:3个实例仅在全局范围内生成1个日志条目。

    您必须为每个组件实例创建一个去抖动函数,而不是在每个组件实例共享的类级别的一个去抖动函数。

    照顾React的事件池

    这是相关的,因为我们经常想要去抖动或限制DOM事件。

    在React中,您在回调中收到的事件对象(即SyntheticEvent)将被合并(现在已记录)。这意味着在调用事件回调之后,您收到的SyntheticEvent将被放回具有空属性的池中以减少GC压力。

    因此,如果您与原始回调异步访问SyntheticEvent属性(如果节流/去抖动可能就是这种情况),则可能会删除您访问的属性。如果您希望永远不会将事件放回池中,则可以使用persist()方法。

    没有持久化(默认行为:池事件)

    1
    2
    3
    4
    5
    6
    onClick = e => {
      alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
      setTimeout(() => {
        alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
      }, 0);
    };

    第二个(异步)将打印hasNativeEvent=false,因为事件属性已被清除。

    坚持不懈

    1
    2
    3
    4
    5
    6
    7
    onClick = e => {
      e.persist();
      alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
      setTimeout(() => {
        alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
      }, 0);
    };

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

    你可以在这里测试这两个行为:JsFiddle

    阅读Julen的答案,了解使用具有油门/去抖功能的persist()的示例。

    好。


    不受控制的组件

    您可以使用event.persist()方法。

    下面使用下划线的_.debounce()示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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()"trickery"。

    JSFiddle也可以使用。没有下划线的示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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 (
           
              <SearchBox query={this.state.result}
                         handleSearch={this.handleSearch} />
              <p>
    You searched for: {this.state.result}
    </p>
           
          );
        }
    });

    React.render(<Search query="Initial query" />, document.body);

    编辑:更新的示例和JSFiddles到React 0.12

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

    编辑:使用jsfiddle更新,不使用下划线并使用普通的javascript去抖。


    如果您需要从事件对象获取DOM输入元素,那么解决方案就更简单了 - 只需使用ref即可。请注意,这需要Underscore:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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} />
        }
    }


    我发现贾斯汀图尔克的这篇文章非常有帮助。经过几次尝试后,人们会认为反应/减少更正式的方式,它表明它由于React的合成事件池而失败。然后,他的解决方案使用一些内部状态来跟踪输入中更改/输入的值,并在setState之后立即进行回调,该回调调用一个实时显示某些结果的限制/去抖动redux操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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}
            />
        )
      }
    }


    在对文本输入进行了一段时间的努力而没有找到完美的解决方案后,我在npm:react-debounce-input上找到了这个。

    这是一个简单的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import React from 'react';
    import ReactDOM from 'react-dom';
    import {DebounceInput} from 'react-debounce-input';

    class App extends React.Component {
    state = {
        value: ''
    };

    render() {
        return (
       
            <DebounceInput
            minLength={2}
            debounceTimeout={300}
            onChange={event => this.setState({value: event.target.value})} />

            <p>
    Value: {this.state.value}
    </p>
       
        );
    }
    }

    const appRoot = document.createElement('div');
    document.body.appendChild(appRoot);
    ReactDOM.render(<App />, appRoot);

    DebounceInput组件接受您可以分配给普通输入元素的所有道具。在codepen上尝试一下

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


    如果您使用redux,您可以使用中间件以非常优雅的方式执行此操作。您可以将Debounce中间件定义为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var timeout;
    export default store => next => action => {
      const { meta = {} } = action;
      if(meta.debounce){
        clearTimeout(timeout);
        timeout = setTimeout(() => {
          next(action)
        }, meta.debounce)
      }else{
        next(action)
      }
    }

    然后,您可以向操作创建者添加去抖动,例如:

    1
    2
    3
    4
    5
    export default debouncedAction = (payload) => ({
      type : 'DEBOUNCED_ACTION',
      payload : payload,
      meta : {debounce : 300}
    }

    实际上已经有中间件你可以下午npm为你做这件事。


    这里有很多好消息,但要简洁明了。这对我有用......

    1
    2
    3
    4
    5
    6
    7
    8
    import React, {Component} from 'react';
    import _ from 'lodash';

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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 (
          <p>
     User typed: {
            this.state.userInput
          }
    </p>
          <input ref ="userValue" onChange = {() => this.updateInput(this.refs.userValue.value) } type ="text" / >
         
        );
      }
    }

    ReactDOM.render( <
      UserInput / > ,
      document.getElementById('root')
    );
    1
    2
    3
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js">


    您可以使用Lodash debounce https://lodash.com/docs/4.17.5#debounce方法。它简单而有效。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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);
    }

    您也可以使用以下方法取消去抖动方法。

    1
    this.debounceHandleUpdate.cancel();

    希望它能帮到你。干杯!!


    使用debounce,您需要使用event.persist()保留原始合成事件。这是使用React 16+测试的工作示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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)
          }}>
            ...
         
        );
      }
    }
    export default ItemType;

    有了功能组件,你可以这样做 -

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const Search = ({ getBooks, query }) => {

      const handleOnSubmit = (e) => {
        e.preventDefault();
      }
      const debouncedGetBooks = debounce(query => {
        getBooks(query);
      }, 700);

      const onInputChange = e => {
        debouncedGetBooks(e.target.value)
      }

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

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


    FYI

    这是另一个PoC实现:

    • 没有任何库(例如lodash)进行去抖动
    • 使用React Hooks API

    我希望它有帮助:)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    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}
        />
      );
    }

    只是另一个变种与最近的反应和lodash。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    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)} />)
      }
    }

    这是我想出的一个例子,用辩护者包装另一个类。这非常适合制作装饰器/高阶函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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} />
        }
    }

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

    1
    2
    3
    4
    5
    handleOnChange: function (event) {
       debounce(
         $.ajax({})
      , 250);
    }


    我正在寻找同一问题的解决方案并且遇到了这个线程以及其他一些但是他们遇到了同样的问题:如果你想要做一个handleOnChange函数而你需要来自事件目标的值,你将会得到cannot read property value of null或一些这样的错误。在我的情况下,我还需要在debounced函数中保留this的上下文,因为我正在执行一个可通行动作。这是我的解决方案,它适用于我的用例,所以我将它留在这里以防万一有人遇到这个帖子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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)

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

    与React Hooks辩论

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


    对于throttleDebounce,最好的方法是创建一个函数创建器,以便您可以在任何地方使用它,例如:

    1
    2
    3
    4
    5
    6
      updateUserProfileField(fieldName) {
        const handler = throttle(value => {
          console.log(fieldName, value);
        }, 400);
        return evt => handler(evt.target.value.trim());
      }

    并在您的render方法中,您可以:

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

    每次调用它时,updateUserProfileField方法都会创建一个单独的函数。

    注意不要尝试直接返回处理程序,例如这不起作用:

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

    之所以不行,是因为每次调用事件时都会生成一个新的油门功能,而不是使用相同的油门功能,所以油门基本没用;)

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


    避免使用event.persist() - 你想让React回收合成事件。我认为使用类或钩子最简洁的方法是将回调拆分为两部分:

  • 没有去抖动的回调
  • 仅使用您需要的事件片段调用去抖动函数(因此可以回收合成事件)
  • 1
    2
    3
    4
    5
    6
    7
    handleMouseOver = throttle(target => {
      console.log(target);
    }, 1000);

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

    职能

    1
    2
    3
    4
    5
    6
    7
    const handleMouseOver = useRef(throttle(target => {
      console.log(target);
    }, 1000));

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

    请注意,如果handleMouseOver函数使用组件内的状态,则应使用useMemo而不是useRef并将其作为依赖项传递,否则您将使用过时数据(当然不适用于类)。


    这是一个有效的TypeScript示例,适用于那些使用TS并想要去抖动async函数的人。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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)
         });
     }


    你可以使用tlence tlence

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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');

    这里有点晚了,但这应该有所帮助。
    创建这个类(它用typescript编写,但很容易将其转换为javascript)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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;
    }

    并使用

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

    Julen解决方案有点难以理解,对于任何根据标题而不是问题的细节而绊倒他的人来说,这里的代码更清晰,更具针对性。

    tl; dr版本:当您更新到观察者时,发送调用调度方法而不是反过来实际上会通知观察者(或执行ajax等)

    使用示例组件jsfiddle完成jsfiddle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    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)
            }
        },

    });


    你也可以使用自编的mixin,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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);
        }
      }
    };

    然后在你的组件中使用它,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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>
        );
      }
    });