如何在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
}
});
有一个使用react钩子的简单方法。
步骤1:定义一个状态来维护搜索的文本
const [searchTerm, setSearchTerm] = useState('')
步骤2:使用useEffect捕获搜索Term中的任何变化
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
if (searchTerm) {
// write your logic here
}
}, 400)
return () => clearTimeout(delayDebounceFn)
}, [searchTerm])
步骤3:编写一个函数来处理输入更改
function handleInputChange(value) {
if (value) {
setSearchTerm(value)
}
}
就这些!在需要时调用此方法
至于2021年6月,您可以简单地实现xnimorz解决方案:https://github.com/xnimorz/use-debounce
import { useState, useEffect, useRef } from "react";
// Usage
function App() {
// State and setters for ...
// Search term
const [searchTerm, setSearchTerm] = useState("");
// API search results
const [results, setResults] = useState([]);
// Searching status (whether there is pending API request)
const [isSearching, setIsSearching] = useState(false);
// Debounce search term so that it only gives us latest value ...
// ... if searchTerm has not been updated within last 500ms.
// The goal is to only have the API call fire when user stops typing ...
// ... so that we aren't hitting our API rapidly.
const debouncedSearchTerm = useDebounce(searchTerm, 500);
// Effect for API call
useEffect(
() => {
if (debouncedSearchTerm) {
setIsSearching(true);
searchCharacters(debouncedSearchTerm).then((results) => {
setIsSearching(false);
setResults(results);
});
} else {
setResults([]);
setIsSearching(false);
}
},
[debouncedSearchTerm] // Only call effect if debounced search term changes
);
return (
<div>
<input
placeholder="Search Marvel Comics"
onChange={(e) => setSearchTerm(e.target.value)}
/>
{isSearching && <div>Searching ...</div>}
{results.map((result) => (
<div key={result.id}>
<h4>{result.title}</h4>
<img
src={`${result.thumbnail.path}/portrait_incredible.${result.thumbnail.extension}`}
/>
</div>
))}
</div>
);
}
// API search function
function searchCharacters(search) {
const apiKey = "f9dfb1e8d466d36c27850bedd2047687";
return fetch(
`https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`,
{
method: "GET",
}
)
.then((r) => r.json())
.then((r) => r.data.results)
.catch((error) => {
console.error(error);
return [];
});
}
// Hook
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
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])
这个解决方案不需要任何额外的库,它也会在用户按下enter时启动:
const debounce = (fn, delay) => {
let timer = null;
return function() {
const context = this,
args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, delay);
};
}
const [search, setSearch] = useState('');
const [searchFor, setSearchFor] = useState(search);
useEffect(() => {
console.log("Search:", searchFor);
}, [searchFor]);
const fireChange = event => {
const { keyCode } = event;
if (keyCode === 13) {
event.preventDefault();
setSearchFor(search);
}
}
const changeSearch = event => {
const { value } = event.target;
setSearch(value);
debounceSetSearchFor(value);
};
const debounceSetSearchFor = useCallback(debounce(function(value) {
setSearchFor(value);
}, 250), []);
输入可以是这样的:
<input value={search} onKeyDown={fireChange} onChange={changeSearch} />
Julen的解决方案有点难以阅读,这里有更清晰和准确的反应代码,供那些根据标题而不是问题的微小细节绊倒他的人使用。
Tl;dr版本:当你要更新到观察者发送调用一个调度方法,反过来实际上会通知观察者(或执行ajax等)
使用示例组件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)
}
},
});