如何在React.js中执行debounce ?
我想撤消handleOnChange。
我尝试debounce(这。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
}
});
我发现Justin Tulk的这篇文章很有帮助。在经过几次尝试后,人们会认为这是react/redux更正式的方式,结果显示它失败了,因为react的合成事件池。然后,他的解决方案使用一些内部状态来跟踪在输入中更改/输入的值,在setState之后使用一个回调,调用一个throttled/ deboundredux动作,实时显示一些结果。
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}
/>
)
}
}
下面是我想出的一个用debouncer包装另一个类的例子。这使得自己很好地成为一个装饰器/高阶函数:
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} />
}
}
I was searching for a solution to the same problem and came across this thread as well as some others but they had the same problem: if you are trying to do a handleOnChange function and you need the value from an event target, you will get cannot read property value of null or some such error. In my case, I also needed to preserve the context of this inside the debounced function since I'm executing a fluxible action. Here's my solution, it works well for my use case so I'm leaving it here in case anyone comes across this thread:
// 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)
/**
* Returns a function with the same signature of input `callback` (but without an output) that if called, smartly
* executes the `callback` in a debounced way.<br>
* There is no `delay` (to execute the `callback`) in the self-delayed tries (try = calling debounced callback). It
* will defer **only** subsequent tries (that are earlier than a minimum timeout (`delay` ms) after the latest
* execution). It also **cancels stale tries** (that have been obsoleted because of creation of newer tries during the
* same timeout).<br>
* The timeout won't be expanded! So **the subsequent execution won't be deferred more than `delay`**, at all.
* @param {Function} callback
* @param {number} [delay=167] Defaults to `167` that is equal to "10 frames at 60 Hz" (`10 * (1000 / 60) ~= 167 ms`)
* @return {Function}
*/
export function smartDebounce (callback, delay = 167) {
let minNextExecTime = 0
let timeoutId
function debounced (...args) {
const now = new Date().getTime()
if (now > minNextExecTime) { // execute immediately
minNextExecTime = now + delay // there would be at least `delay` ms between ...
callback.apply(this, args) // ... two consecutive executions
return
}
// schedule the execution:
clearTimeout(timeoutId) // unset possible previous scheduling
timeoutId = setTimeout( // set new scheduling
() => {
minNextExecTime = now + delay // there would be at least `delay` ms between ...
callback.apply(this, args) // ... two consecutive executions
},
minNextExecTime - now, // 0 <= timeout <= `delay` ... (`minNextExecTime` <= `now` + `delay`)
)
}
debounced.clear = clearTimeout.bind(null, timeoutId)
return debounced
}
/**
* Like React's `useCallback`, but will {@link smartDebounce smartly debounce} future executions.
* @param {Function} callback
* @param {[]} deps
* @param {number} [delay=167] - Defaults to `167` that is equal to "10 frames at 60 Hz" (`10 * (1000 / 60) ~= 167 ms`)
*/
export const useDebounced = (callback, deps, delay = 167) =>
useMemo(() => smartDebounce(callback, delay), [...deps, delay])
你可以在ReactJS钩子中使用一个use-debounce包。
从包的README:
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000);
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>Actual value: {text}</p>
<p>Debounce value: {value}</p>
</div>
);
}
从上面的示例中可以看到,它设置为每秒钟更新一次变量值(1000毫秒)。
今天遇到了这个问题。使用setTimeout和clearTimeout解决。
我将给出一个你可以适应的例子:
import React, { Component } from 'react'
const DEBOUNCE_TIME = 500
class PlacesAutocomplete extends Component {
debounceTimer = null;
onChangeHandler = (event) => {
// Clear the last registered timer for the function
clearTimeout(this.debounceTimer);
// Set a new timer
this.debounceTimer = setTimeout(
// Bind the callback function to pass the current input value as arg
this.getSuggestions.bind(null, event.target.value),
DEBOUNCE_TIME
)
}
// The function that is being debounced
getSuggestions = (searchTerm) => {
console.log(searchTerm)
}
render() {
return (
<input type="text" onChange={this.onChangeHandler} />
)
}
}
export default PlacesAutocomplete
你也可以在它自己的函数组件中重构它:
import React from 'react'
function DebouncedInput({ debounceTime, callback}) {
let debounceTimer = null
return (
<input type="text" onChange={(event) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(
callback.bind(null, event.target.value),
debounceTime
)
}} />
)
}
export default DebouncedInput
像这样使用它:
import React, { Component } from 'react'
import DebouncedInput from '../DebouncedInput';
class PlacesAutocomplete extends Component {
debounceTimer = null;
getSuggestions = (searchTerm) => {
console.log(searchTerm)
}
render() {
return (
<DebouncedInput debounceTime={500} callback={this.getSuggestions} />
)
}
}
export default PlacesAutocomplete