在React.js中执行反跳

本文翻译自:Perform debounce in React.js

How do you perform debounce in React.js? 您如何在React.js中执行反跳?

I want to debounce the handleOnChange. 我想对handleOnChange进行反跳。

I tried with debounce(this.handleOnChange, 200) but it doesn't work. 我尝试使用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
  }
});

#1楼

参考:https://stackoom.com/question/1Z1Nq/在React-js中执行反跳


#2楼

Instead of wrapping the handleOnChange in a debounce(), why not wrap the ajax call inside the callback function inside the debounce, thereby not destroying the event object. 与其将handleOnChange包装在debounce()中,不如将ajax调用包装在debounce中的回调函数中,从而不破坏事件对象。 So something like this: 所以像这样:

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

#3楼

Uncontrolled Components 不受控制的组件

You can use the event.persist() method . 您可以使用event.persist()方法

An example follows using underscore's _.debounce() : 下面是使用下划线的_.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} />
    );
  }

});

Edit: See this JSFiddle 编辑:看到此JSFiddle


Controlled Components 受控组件

Update: the example above shows an uncontrolled component . 更新:上面的示例显示了不受控制的组件 I use controlled elements all the time so here's another example of the above, but without using the event.persist() "trickery". 我一直在使用受控元素,因此这是上面的另一个示例,但是没有使用event.persist() “ trickery”。

A JSFiddle is available as well. 也可以使用JSFiddle 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);

Edit: updated examples and JSFiddles to React 0.12 编辑:将示例和JSFiddles更新为React 0.12

Edit: updated examples to address the issue raised by Sebastien Lorber 编辑:更新了示例以解决Sebastien Lorber提出的问题

Edit: updated with jsfiddle that does not use underscore and uses plain javascript debounce. 编辑:使用不使用下划线并使用普通JavaScript反跳的jsfiddle更新。


#4楼

Julen solution is kind of hard to read, here's clearer and to-the-point react code for anyone who stumbled him based on title and not the tiny details of the question. Julen解决方案有点难以阅读,对于那些根据标题而不是问题的微小细节绊倒他的人,这里提供了更清晰,更准确的反应代码。

tl;dr version : when you would update to observers send call a schedule method instead and that in turn will actually notify the observers (or perform ajax, etc) tl; dr版本 :当您要更新给观察者时,请发送调用schedule方法,而该方法实际上会通知观察者(或执行ajax等)

Complete jsfiddle with example component jsfiddle 使用示例组件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)
        }
    },

});

#5楼

You can also use a self-written mixin, something like this: 您还可以使用自写的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);
    }
  }
};

And then use it in your component like this: 然后像这样在组件中使用它:

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

#6楼

2019: try hooks + promise debouncing 2019:尝试钩子+承诺反跳

This is the most up to date version of how I would solve this problem. 这是我如何解决此问题的最新版本。 I would use: 我会用:

This is some initial wiring but you are composing primitive blocks on your own, and you can make your own custom hook so that you only need to do this once. 这是一些初始接线,但是您可以自己构成基本块,并且可以制作自己的自定义钩子,因此只需执行一次即可。

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 () => {
      if (inputText.length === 0) {
        return [];
      } else {
        return debouncedSearchStarwarsHero(inputText);
      }
    },
    // 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,
  };
};

And then you can use your hook: 然后可以使用您的钩子:

const SearchStarwarsHeroExample = () => {
  const { inputText, setInputText, search } = useSearchStarwarsHero();
  return (
    <div>
      <input value={inputText} onChange={e => setInputText(e.target.value)} />
      <div>
        {search.loading && <div>...</div>}
        {search.error && <div>Error: {search.error.message}</div>}
        {search.result && (
          <div>
            <div>Results: {search.result.length}</div>
            <ul>
              {search.result.map(hero => (
                <li key={hero.name}>{hero.name}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  );
};

You will find this example running here and you should read react-async-hook documentation for more details. 您将发现此示例在此处运行并且您应该阅读react-async-hook文档以获取更多详细信息。


2018: try promise debouncing 2018年:尝试Promise反弹

We often want to debounce API calls to avoid flooding the backend with useless requests. 我们通常希望对API调用进行去抖动,以避免后端无用的请求泛滥。

In 2018, working with callbacks (Lodash/Underscore) feels bad and error-prone to me. 在2018年,使用回调(Lodash / Underscore)感到糟糕并且容易出错。 It's easy to encounter boilerplate and concurrency issues due to API calls resolving in an arbitrary order. 由于API调用以任意顺序进行解析,因此很容易遇到样板和并发问题。

I've created a little library with React in mind to solve your pains: awesome-debounce-promise . 我已经创建了一个小库来考虑React的问题: awesome-debounce-promise

This should not be more complicated than that: 这不应该比这更复杂:

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

The debounced function ensures that: 去抖动功能可确保:

  • API calls will be debounced API调用将被删除
  • the debounced function always returns a promise 去抖功能总是返回一个承诺
  • only the last call's returned promise will resolve 只有最后一次通话的返回承诺会解决
  • a single this.setState({ result }); 一个this.setState({ result }); will happen per API call 每个API调用都会发生

Eventually, you may add another trick if your component unmounts: 最终,如果卸载了组件,您可能会添加另一个技巧:

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

Note that Observables (RxJS) can also be a great fit for debouncing inputs, but it's a more powerful abstraction which may be harder to learn/use correctly. 请注意, Observables (RxJS)也可以非常适合去抖动输入,但是它是更强大的抽象,可能更难于正确学习/使用。


< 2017: still want to use callback debouncing? <2017:仍要使用回调反跳吗?

The important part here is to create a single debounced (or throttled) function per component instance . 这里的重要部分是为每个组件实例创建一个单独的去抖动(或抑制)功能 You don't want to recreate the debounce (or throttle) function everytime, and you don't want either multiple instances to share the same debounced function. 您不想每次都重新创建去抖动(或调节)功能,也不想多个实例共享相同的去抖动功能。

I'm not defining a debouncing function in this answer as it's not really relevant, but this answer will work perfectly fine with _.debounce of underscore or lodash, as well as any user-provided debouncing function. 我不会在这个答案定义防抖动功能,因为它不是真正相关的,但这个答案将与完美的罚款_.debounce下划线或lodash的,以及任何用户提供防抖动功能。


GOOD IDEA: 好主意:

Because debounced functions are stateful, we have to create one debounced function per component instance . 因为去抖功能是有状态的,所以我们必须为每个组件实例创建一个去抖功能

ES6 (class property) : recommended ES6(类属性) :推荐

class SearchBox extends React.Component {
    method = debounce(() => { 
      ...
    });
}

ES6 (class constructor) ES6(类构造函数)

class SearchBox extends React.Component {
    constructor(props) {
        super(props);
        this.method = debounce(this.method.bind(this),1000);
    }
    method() { ... }
}

ES5 ES5

var SearchBox = React.createClass({
    method: function() {...},
    componentWillMount: function() {
       this.method = debounce(this.method.bind(this),100);
    },
});

See JsFiddle : 3 instances are producing 1 log entry per instance (that makes 3 globally). 请参阅JsFiddle :3个实例每个实例产生1个日志条目(全局产生3个)。


NOT a good idea: 这不是一个好主意:

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

It won't work, because during class description object creation, this is not the object created itself. 它不起作用,因为在类描述对象创建期间, this不是对象本身创建的。 this.method does not return what you expect because the this context is not the object itself (which actually does not really exist yet BTW as it is just being created). this.method不会返回您期望的结果,因为this上下文不是对象本身(实际上并没有真正存在,因为它只是被创建而已)。


NOT a good idea: 这不是一个好主意:

var SearchBox = React.createClass({
  method: function() {...},
  debouncedMethod: function() {
      var debounced = debounce(this.method,100);
      debounced();
  },
});

This time you are effectively creating a debounced function that calls your this.method . 这次您实际上是在创建一个去抖动的函数,该函数调用您的this.method The problem is that you are recreating it on every debouncedMethod call, so the newly created debounce function does not know anything about former calls! 问题是您在每个debouncedMethod调用中都在重新创建它,因此新创建的debounce函数对以前的调用一无所知! You must reuse the same debounced function over time or the debouncing will not happen. 您必须随着时间的推移重复使用相同的防抖动功​​能,否则防抖动将不会发生。


NOT a good idea: 这不是一个好主意:

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

This is a little bit tricky here. 这有点棘手。

All the mounted instances of the class will share the same debounced function, and most often this is not what you want!. 该类的所有已安装实例将共享相同的去抖动功能,通常,这不是您想要的! See JsFiddle : 3 instances are producting only 1 log entry globally. 请参阅JsFiddle :3个实例在全球范围内仅产生1个日志条目。

You have to create a debounced function for each component instance , and not a single debounced function at the class level, shared by each component instance. 您必须为每个组件实例创建一个去抖动功能,而不是在每个类实例共享的类级别上创建一个单独的去抖动功能。


Take care of React's event pooling 照顾好React的事件池

This is related because we often want to debounce or throttle DOM events. 这是相关的,因为我们经常想去抖动或限制DOM事件。

In React, the event objects (ie, SyntheticEvent ) that you receive in callbacks are pooled (this is now documented ). 在React中,您在回调中收到的事件对象(即SyntheticEvent )将被合并(现在已记录 )。 This means that after the event callback has be called, the SyntheticEvent you receive will be put back in the pool with empty attributes to reduce the GC pressure. 这意味着在调用事件回调之后,您收到的SyntheticEvent将被放回具有空属性的池中,以减少GC压力。

So if you access SyntheticEvent properties asynchronously to the original callback (as may be the case if you throttle/debounce), the properties you access may be erased. 因此,如果您以与原始回调异步的方式访问SyntheticEvent属性(例如,通过油门/反跳操作可能会发生这种情况),则可能会删除您访问的属性。 If you want the event to never be put back in the pool, you can use the persist() method. 如果您希望事件永不放回池中,则可以使用persist()方法。

Without persist (default behavior: pooled event) 没有持久(默认行为:池事件)

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

The 2nd (async) will print hasNativeEvent=false because the event properties have been cleaned up. 第二个(异步)将打印hasNativeEvent=false因为事件属性已被清除。

With persist 与坚持

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

The 2nd (async) will print hasNativeEvent=true because persist allows you to avoid putting the event back in the pool. 第二个(异步)将打印hasNativeEvent=true因为persist使您可以避免将事件放回池中。

You can test these 2 behaviors here: JsFiddle 您可以在此处测试这两种行为: JsFiddle

Read Julen's answer for an example of using persist() with a throttle/debounce function. 阅读Julen的答案 ,以了解如何将persist()与节流/去抖功能一起使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值