你如何在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
}
});
16 回答
2018年:尝试承诺去债
我们经常想要去掉api调用,以避免用无用的请求充斥后端 .
在2018年,仍然使用回调(Lodash / Underscore)感觉不好并且容易出错 . 由于api调用解决了无序问题,很容易遇到样板和并发问题 .
我已经创建了一个带有React的小库来解决你的痛苦:awesome-debounce-promise
这不应该比那更复杂:
去抖动功能可确保:
api电话将被撤销
debounced函数总是返回一个promise
只有最后一次调用的返回承诺才会解决
每个api-call会发生一次
this.setState({ result });
最后,如果您的组件卸载,您可以添加另一个技巧:
请注意, Observables (RxJS)也非常适合去抖动输入,但它是一种更强大的抽象,可能更难以正确学习/使用 .
还想使用回调去抖动吗?
这里重要的部分是 to create a single debounced (or throttled) function per component instance . 您不希望多个实例共享相同的去抖功能 .
我没有真正相关,但是这个答案对于
_.debounce
的下划线或lodash,以及用户提供的去抖动功能都可以完美地运行 .不是个好主意:
它不起作用,因为在类描述对象创建期间,
this
不是自己创建的对象 .this.method
没有返回你期望的内容,因为this
上下文不是对象本身(它实际上并不存在BTW,因为它刚刚被创建) .不是个好主意:
这次你有效地创建了一个调用
this.method
的去抖动函数 . 问题是你在每次debouncedMethod
呼叫上重新创建它,所以新创建的去抖功能对以前的呼叫一无所知!您必须重复使用相同的去抖功能,否则不会发生去抖动 .不是个好主意:
这在这里有点棘手 .
该类的所有已安装实例将共享相同的去抖功能,并且通常这不是您想要的!请参阅JsFiddle:3个实例仅在全局范围内生成1个日志条目 .
你必须创建一个去抖动函数 for each component instance ,而不是在每个组件实例共享的类级别的单个去抖动函数 .
好想法:
因为debounced函数是有状态的,我们必须创建 one debounced function per component instance .
ES6 (class property) :推荐
ES6 (class constructor)
ES5
请参阅JsFiddle:3个实例正在为每个实例生成1个日志条目(全局生成3个) .
照顾React的事件池
这是相关的,因为我们经常想要去抖动或限制DOM事件 .
在React中,您在回调中收到的事件对象(即
SyntheticEvent
)将被合并(现在为documented) . 这意味着在调用事件回调之后,您收到的SyntheticEvent将被放回具有空属性的池中以减少GC压力 .因此,如果您访问原始回调的SyntheticEvent属性异步(如果您节流/去抖动可能就是这种情况),您访问的属性可能会被删除 . 如果您希望永远不会将事件放回池中,则可以使用
persist()
方法 .没有持久化(默认行为:池化事件)
第二个(异步)将打印
hasNativeEvent=false
,因为事件属性已被清除 .坚持不懈
第二个(异步)将打印
hasNativeEvent=true
因为persist()允许避免将事件放回池中 .你可以在这里测试这两个行为JsFiddle
请阅读Julen's answer,了解使用具有油门/去抖功能的
persist()
的示例 .不受控制的组件
你可以使用event.persist() method .
下面使用下划线的
_.debounce()
示例:编辑:见this JSFiddle
受控组件
更新:上面的示例显示uncontrolled component . 我一直使用受控元素,所以这是上面的另一个例子,但没有使用
event.persist()
"trickery" .A JSFiddle is available也是如此 . Example without underscore
编辑:更新的示例和JSFiddles到React 0.12
编辑:更新示例以解决Sebastien Lorber提出的问题
编辑:使用jsfiddle更新,不使用下划线并使用普通的javascript去抖动 .
如果您需要从事件对象获取DOM输入元素,那么解决方案就更简单了 - 只需使用
ref
我发现Justin Tulk的this post非常有帮助 . 经过几次尝试之后,人们会认为是反应/减少更正式的方式,它表明它由于React's synthetic event pooling而失败 . 然后他的解决方案使用一些内部状态来跟踪 Value 在输入中更改/输入,在
setState
之后立即进行回调,该回调调用一个实时显示某些结果的限制/去抖动redux操作 .经过一段时间的文本输入挣扎而没有找到我自己的完美解决方案后,我发现这个在npm https://www.npmjs.com/package/react-debounce-input
这是一个简单的例子:
DebounceInput组件接受您可以分配给普通输入元素的所有道具 . 试试codepen
我希望它也可以帮助别人并节省一些时间 .
使用ES6 CLASS和 React 15.x.x &lodash.debounce我在这里使用React的 refs ,因为事件在内部丢失了这个绑定 .
如果您使用redux,您可以使用中间件以非常优雅的方式执行此操作 . 您可以将
Debounce
中间件定义为:然后,您可以向操作创建者添加去抖动,例如:
实际上already middleware你可以下午npm为你做这件事 .
你可以使用Lodash去抖https://lodash.com/docs/4.17.5#debounce方法 . 它简单而有效 .
您也可以使用以下方法取消去抖动方法 .
希望它能帮到你 . 干杯!!
这里有很多好的信息,但要简洁 . 这对我有用......
这是我想出的一个例子,用辩护者包装另一个类 . 这非常适合制作装饰器/高阶函数:
而不是将handleOnChange包装在debounce()中,为什么不将ajax调用包装在debounce内部的回调函数内,从而不会破坏事件对象 . 所以这样的事情:
我正在寻找同一个问题的解决方案并且遇到了这个线程以及其他一些但是他们遇到了同样的问题:如果你想做一个
handleOnChange
函数并且你需要一个事件目标的值,你将得到cannot read property value of null
或者一些这样的错误 . 在我的情况下,我还需要在debounced函数中保留this
的上下文,因为我是我的解决方案,它适用于我的用例所以我将它留在这里以防万一有人遇到这个帖子:对于
throttle
或debounce
,最好的方法是创建一个函数创建器,以便您可以在任何地方使用它,例如:在您的
render
方法中,您可以:updateUserProfileField
方法每次调用时都会创建一个单独的函数 .Note 不要尝试直接返回处理程序,例如这不起作用:
之所以这不起作用,是因为每次调用该事件而不是使用相同的油门功能时都会产生一个新的油门功能,所以油门基本没用;)
此外,如果您使用
debounce
或throttle
,您不需要setTimeout
或clearTimeout
,这实际上是我们使用它们的原因:PJulen解决方案有点难以阅读,对于任何根据 Headers 而不是问题的微小细节而绊倒他的人来说,这里的代码更清晰,更具针对性 .
tl;dr version :当您更新到观察者时,发送调用调度方法而不是反过来将实际通知观察者(或执行ajax等)
用示例组件完成jsfiddle http://jsfiddle.net/7L655p5L/4/
你也可以使用自编的mixin,如下所示:
然后在你的组件中使用它,如下所示:
一个人不需要大量的局部变量来获得合适的油门功能 . 节流功能的目的是减少浏览器资源,而不是应用你正在使用的更多开销 . 作为这种说法的证据的证明,我设计了一种油门功能,其范围内只有4个"hanging"变量 . ("hanging"变量是一个永远不会被垃圾收集的变量,因为它总是被一个可能被调用的函数引用,从而吸收内存 . )少数油门功能通常不会造成任何伤害;但是,如果有数千个节流功能,那么如果使用非常低效的油门功能,内存就会变得稀缺 . 我的解决方案如下 .
然后,围绕EventTarget包装此限制函数,例如DOM点击,窗口事件,XMLHttpRequests onprogress,FileReader onprogress,[等],如下所示:
下面是一个按钮的示例,该按钮被限制为每秒仅侦听一次点击 . 当它收到此单击时,它将更改为新的随机颜色 .
保存这个答案之后,我发现有毒SO社区的持久性downvotes阻止了这个片段的运行 . 因此,这是一个JSFiddle的链接:https://jsfiddle.net/t7ymkzLx/2/
默认情况下,此功能限制为每200ms最多一次调用 . 要将间隔更改为不同的毫秒数,请在options参数中传递
optionsObject.INTERVAL
选项,并将其设置为执行之间所需的最小毫秒数 . (因为计时器并不总是最多的精确的,)如果你有一个确切的最小间隔,那么我建议你从所需的optionsObject.INTERVAL
中减去一两个,以确保它总是至少应该被执行 . 如果在受限制的函数执行被延迟时(由于过多的调用),您需要对限制的func的参数执行某些操作,请使用optionsObject.ALTERNATE
选项 . 这个"ALTERNATE"是一个函数,只要删除对主函数的调用,就会立即调用它来代替主函数 . 例如,如果您在EventTarget上使用限制函数,但想要在已删除事件上使用preventDefault()
,则使用{ALTERNATE: function(evt){ evt.preventDefault(); }}
作为选项对象 .