什么是 debounce
debounce,中文翻译为防抖。与之相对应的还有一个概念 throttle —— 节流。两者用来控制某个函数在一定时间内执行多少次的技巧,相似却又不同。
关于两者的对比以及使用场景,可以阅读这篇文章来加深了解。本文主要讲述的,是如果在 react components
中,使用 debounce
函数来防止回调事件的多次触发,从而提升代码效率。
应用
在一个 input
输入框中,给其绑定了一个 onChange
事件,每次输入后,都会触发一个回调函数。因为我们想要的只是用户输入的最后结果,所以除了用户输入完成后触发的回调函数,其他都是冗余的。因此,需要对绑定的回调函数做 debounce
处理。
关于 debounce
函数,有很多实现版本,这里选用了 loadsh.debounce
。虽然函数实现原理很简单,但是用于生产环境的代码,还是建议使用成熟的第三方库。
我们的第一反应,一般都是直接将回调函数用 debounce
包裹起来达到想要的效果。
import react, { Component } from 'react';
import { debounce } from 'lodash';
export default class Debounce extends Component {
printChange(e) {
console.log('value :: ', e.target.value);
// call ajax
}
render() {
return (
<div>
<input onChange={debounce(this.printChange, 500)} />
</div>
);
}
}
但是这么做之后,浏览器就会报异常: Uncaught TypeError: Cannot read property 'value' of null
。为什么会这样?这里就涉及到了 react
事件系统 中的一个概念:合成事件。
合成事件(SyntheticEvent)
事件处理程序通过 合成事件(SyntheticEvent)的实例传递,SyntheticEvent
是浏览器原生事件跨浏览器的封装。SyntheticEvent
和浏览器原生事件一样有 stopPropagation()
、preventDefault()
接口,而且这些接口夸浏览器兼容。
事件池(Event Pooling)
SyntheticEvent
是池化的. 这意味着 SyntheticEvent
对象将会被重用,并且所有的属性都会在事件回调被调用后被 nullified。 这是因为性能的原因。 因此,你不能异步的访问事件。
通过了解事件系统,也就不难理解为什么会报错了。因为经过 debounce
包装后的回调函数,变成了一个异步事件,在池化后被 nullified 了。
那么怎样才能解决这个问题?
通过在回调事件顶部加上 e.persist()
就可以从池中移除合成事件,并允许对事件的引用保留。
import react, { Component } from 'react';
import { debounce } from 'lodash';
export default class Debounce extends Component {
printChange(e) {
e.persist();
debounce(() => {
console.log('value :: ', e.target.value);
// call ajax
});
}
render() {
return (
<div>
<input onChange={this.printChange} />
</div>
);
}
}
这样做之后报错问题虽然解决了,但是会发现 debounce
的函数并没有被执行。因此,还需要对回调函数进行优化。
把需要异步执行的回调函数抽离出来封装,并且在组件初始化话的时候就将其 debounce
化,就可以得到我们想要的效果。
import react, { Component } from 'react';
import { debounce } from 'lodash';
export default class Debounce extends Component {
construtor() {
super();
this.callAjax = debounce(this.callAjax, 300);
}
callAjax = (value) => {
console.log('value :: ', value);
// call ajax
}
printChange(e) {
e.persist();
this.callAjax(e.target.value);
}
render() {
return (
<div>
<input onChange={this.printChange} />
</div>
);
}
}
throttle的使用方法和debounce的使用方法一样