在React.js中执行去抖动
你如何在React.js中执行debounce?
我想去除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 } });
这里的重要部分是为每个组件实例创build一个单一的去抖(或限制)函数 。 您不希望每次都重新创build去抖动(或油门)function,而且也不希望多个实例共享相同的去抖function。
我没有定义在这个答案debouncing函数,因为它不是真正相关的,但是这个答案将工作完全正确与_.debounce
的下划线或lodash,以及用户提供的去抖动function。
不是一个好主意:
var SearchBox = React.createClass({ method: function() {...}, debouncedMethod: debounce(this.method,100); });
这是行不通的,因为在创build类描述对象的时候, this
不是自己创build的对象。 this.method
不会返回你所期望的,因为this
上下文不是对象本身(它实际上并不真正存在,因为它正在创buildBTW)。
不是一个好主意:
var SearchBox = React.createClass({ method: function() {...}, debouncedMethod: function() { var debounced = debounce(this.method,100); debounced(); }, });
这次你正在创build一个调用你的this.method
的去抖动函数。 问题是,你正在重新创build每个debouncedMethod
调用,所以新创build的debounce函数不知道任何关于以前的调用! 您必须随时间重复使用相同的去抖function,否则将不会发生去抖动。
不是一个好主意:
var SearchBox = React.createClass({ debouncedMethod: debounce(function () {...},100), });
这里有点棘手。
该类的所有挂载的实例将共享相同的去抖function,并且大多数情况下这不是你想要的! 请参阅JsFiddle :3个实例仅在全局生成1个日志条目。
您必须为每个组件实例创build一个去抖动函数,而不是在每个组件实例共享的类级别的单个去抖动函数。
好主意:
因为去抖动函数是有状态的,所以我们必须为每个组件实例创build一个去抖动函数 。
ES6(class级物业) :推荐
class SearchBox extends React.Component { method = debounce(() => { ... }); }
ES6(类构造函数)
class SearchBox extends React.Component { constructor(props) { super(props); this.method = debounce(this.method,1000); } method() { ... } }
ES5
var SearchBox = React.createClass({ method: function() {...}, componentWillMount: function() { this.method = debounce(this.method,100); }, });
请参阅JsFiddle :3个实例正在生成每个实例1个日志条目(全局3个)。
照顾React的事件池
这是相关的,因为我们经常要去抖或扼杀DOM事件。
在React中,您在callback中收到的事件对象(即SyntheticEvent
)将被合并(现在已经logging )。 这意味着在事件callback被调用之后,您接收到的SyntheticEvent将被放回到具有空属性的池中,以降低GC压力。
所以如果你访问SyntheticEvent属性asynchronous到原来的callback(因为它可能是这种情况,如果油门/去抖动),您访问的属性可能会被删除。 如果您希望事件永远不会放回池中,则可以使用persist()
方法。
没有坚持(默认行为:池事件)
onClick = e => { alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`); setTimeout(() => { alert(`async -> hasNativeEvent=${!!e.nativeEvent}`); }, 0); };
第二个(asynchronous)将打印hasNativeEvent=false
因为事件属性已被清除。
坚持
onClick = e => { e.persist(); alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`); setTimeout(() => { alert(`async -> hasNativeEvent=${!!e.nativeEvent}`); }, 0); };
第二个(asynchronous)将打印hasNativeEvent=true
因为persist()允许避免将事件放回池中。
你可以在这里testing这两个行为JsFiddle
阅读Julen的答案 ,以油门/去抖function使用persist()
。
不受控制的组件
你可以使用event.persist()
方法 。
下面的示例使用下划线的_.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} /> ); } });
编辑:看到这个JSFiddle
受控组件
更新:上面的例子显示了一个不受控制的组件 。 我总是使用受控元素,所以这里是上面的另一个例子,但是不使用event.persist()
“trickery”。
JSFiddle也是可用的 。 没有下划线的例子
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提出的问题
编辑:更新与jsfiddle,不使用下划线,并使用纯JavaScript的反弹。
如果您只需从事件对象获取DOMinput元素,则解决scheme要简单得多 – 只需使用ref
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} /> } }
下面是我提出的一个例子,用另一个类去掉了一个类。 这很适合做成装饰器/高阶函数:
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} /> } }
我发现Justin Tulk 这个post非常有帮助。 经过一番尝试后,人们会认为这是更为正式的反应/还原的方式,这表明它由于React的综合事件池而失败。 然后,他的解决scheme使用一些内部状态来跟踪input中更改/input的值,并在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} /> ) } }
为了避免在debounce()中包装handleOnChange,为什么不把ajax调用包含在debounce中的callback函数内部,从而不会破坏事件对象。 所以像这样的东西:
handleOnChange: function (event) { debounce( $.ajax({}) , 250); }
我正在寻找同样的问题的解决scheme,并遇到这个线程以及一些其他人,但他们也有同样的问题:如果你正在尝试做一个handleOnChange
函数,你需要从事件目标的值,你将cannot read property value of null
或某些这样的错误的cannot read property value of null
。 在我的情况下,我还需要保留在去抖函数内部的上下文,因为我正在执行一个可变动作。 这是我的解决scheme,它适用于我的用例,所以我留在这里以防万一有人遇到这个线程:
// 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)
如果你正在使用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) } }
然后,您可以将动作添加到动作创build者,例如:
export default debouncedAction = (payload) => ({ type : 'DEBOUNCED_ACTION', payload : payload, meta : {debounce : 300} }
实际上已经有中间件,你可以下npm为你做这个。
Julen的解决scheme有点难以阅读,对于那些以标题为基础而不是微小细节的人来说,这里有更清晰,更有针对性的反应代码。
tl; dr版本 :当你更新到观察者发送调用一个时间表方法,而反过来将实际上通知观察者(或执行AJAX等)
用示例组件完成jsfiddle http://jsfiddle.net/7L655p5L/4/
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,如下所示:
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); } } };
然后在你的组件中像这样使用它:
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> ); } });
对于throttle
或debounce
,最好的方法是创build一个函数创build器,以便在任何地方使用它,例如:
updateUserProfileField(fieldName) { const handler = throttle(value => { console.log(fieldName, value); }, 400); return evt => handler(evt.target.value.trim()); }
在你的render
方法中你可以这样做:
<input onChange={this.updateUserProfileField("givenName").bind(this)}/>
updateUserProfileField
方法将在您每次调用它时创build一个分离的函数。
注意不要试图直接返回处理程序,例如这不起作用:
updateUserProfileField(fieldName) { return evt => throttle(value => { console.log(fieldName, value); }, 400)(evt.target.value.trim()); }
之所以这样做是行不通的,因为每次调用这个事件都会产生一个新的油门function,而不是使用相同的油门function,所以油门基本上是无用的;)
另外,如果你使用debounce
或者throttle
你不需要setTimeout
或clearTimeout
,这就是我们为什么使用它们的原因:P
经过一段时间的文字input挣扎,并没有find一个完美的解决scheme,我发现这在npm https://www.npmjs.com/package/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组件接受所有可以分配给普通input元素的道具。 在codepen上试试看
我希望它也能帮助别人,节省一些时间。