你如何在 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
}
});
debounce
方式。这里,当 onChange={debounce(this.handleOnChange, 200)}/>
时,它每次都会调用 debounce function
。但是,实际上,我们需要的是调用 debounce 函数返回的函数。
2019:尝试钩子+承诺去抖动
这是我如何解决这个问题的最新版本。我会使用:
awesome-debounce-promise 去抖动异步函数
使用常量将去抖函数存储到组件中
react-async-hook 将结果放入我的组件中
这是一些初始接线,但您正在自己组合原始块,您可以制作自己的自定义钩子,这样您只需要这样做一次。
// Generic reusable hook
const useDebouncedSearch = (searchFunction) => {
// Handle the input text state
const [inputText, setInputText] = useState('');
// Debounce the original search async function
const debouncedSearchFunction = useConstant(() =>
AwesomeDebouncePromise(searchFunction, 300)
);
// The async callback is run each time the text changes,
// but as the search function is debounced, it does not
// fire a new request on each keystroke
const searchResults = useAsync(
async () => {
if (inputText.length === 0) {
return [];
} else {
return debouncedSearchFunction(inputText);
}
},
[debouncedSearchFunction, inputText]
);
// Return everything needed for the hook consumer
return {
inputText,
setInputText,
searchResults,
};
};
然后你可以使用你的钩子:
const useSearchStarwarsHero = () => useDebouncedSearch(text => searchStarwarsHeroAsync(text))
const SearchStarwarsHeroExample = () => {
const { inputText, setInputText, searchResults } = useSearchStarwarsHero();
return (
<div>
<input value={inputText} onChange={e => setInputText(e.target.value)} />
<div>
{searchResults.loading && <div>...</div>}
{searchResults.error && <div>Error: {search.error.message}</div>}
{searchResults.result && (
<div>
<div>Results: {search.result.length}</div>
<ul>
{searchResults.result.map(hero => (
<li key={hero.name}>{hero.name}</li>
))}
</ul>
</div>
)}
</div>
</div>
);
};
您会发现此示例正在运行 here,您应该阅读 react-async-hook 文档以了解更多详细信息。
2018:尝试去抖动
我们经常希望对 API 调用进行去抖动处理,以避免无用请求淹没后端。
在 2018 年,使用回调(Lodash/Underscore)对我来说感觉很糟糕并且容易出错。由于 API 调用以任意顺序解析,很容易遇到样板和并发问题。
我创建了一个带有 React 的小库来解决您的烦恼:awesome-debounce-promise。
这不应该比这更复杂:
const searchAPI = text => fetch('/search?text=' + encodeURIComponent(text));
const searchAPIDebounced = AwesomeDebouncePromise(searchAPI, 500);
class SearchInputAndResults extends React.Component {
state = {
text: '',
results: null,
};
handleTextChange = async text => {
this.setState({ text, results: null });
const result = await searchAPIDebounced(text);
this.setState({ result });
};
}
去抖功能确保:
API 调用将被去抖动
debounced 函数总是返回一个 promise
只有最后一个调用返回的承诺会解决
单个 this.setState({ result });每次 API 调用都会发生
最后,如果您的组件卸载,您可能会添加另一个技巧:
componentWillUnmount() {
this.setState = () => {};
}
请注意,Observables (RxJS) 也非常适合去抖动输入,但它是一种更强大的抽象,可能更难正确学习/使用。
< 2017: 还想使用回调去抖动?
这里的重要部分是为每个组件实例创建一个去抖动(或节流)函数。您不想每次都重新创建去抖(或油门)功能,也不希望多个实例共享相同的去抖功能。
我没有在此答案中定义去抖功能,因为它并不真正相关,但此答案将与下划线或 lodash 的 _.debounce
以及任何用户提供的去抖功能完美配合。
好主意:
因为去抖函数是有状态的,我们必须为每个组件实例创建一个去抖函数。
ES6(类属性):推荐
class SearchBox extends React.Component {
method = debounce(() => {
...
});
}
ES6(类构造函数)
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.method = debounce(this.method.bind(this),1000);
}
method() { ... }
}
ES5
var SearchBox = React.createClass({
method: function() {...},
componentWillMount: function() {
this.method = debounce(this.method.bind(this),100);
},
});
请参阅 JsFiddle:3 个实例为每个实例生成 1 个日志条目(全局生成 3 个)。
不是一个好主意:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: debounce(this.method, 100);
});
它不起作用,因为在创建类描述对象期间,this
不是自己创建的对象。 this.method
不会返回您所期望的,因为 this
上下文不是对象本身(实际上它实际上并不存在,顺便说一句,因为它刚刚被创建)。
不是一个好主意:
var SearchBox = React.createClass({
method: function() {...},
debouncedMethod: function() {
var debounced = debounce(this.method,100);
debounced();
},
});
这次您实际上是在创建一个调用 this.method
的去抖动函数。问题是您在每次 debouncedMethod
调用时都重新创建它,因此新创建的 debounce 函数对以前的调用一无所知!您必须随着时间的推移重复使用相同的去抖动功能,否则不会发生去抖动。
不是一个好主意:
var SearchBox = React.createClass({
debouncedMethod: debounce(function () {...},100),
});
这里有点棘手。
类的所有挂载实例都将共享相同的去抖动功能,而且大多数情况下这不是您想要的!请参阅 JsFiddle:3 个实例在全球仅产生 1 个日志条目。
您必须为每个组件实例创建一个去抖动函数,而不是在类级别创建一个由每个组件实例共享的去抖动函数。
照顾 React 的事件池
这是相关的,因为我们经常想要去抖动或限制 DOM 事件。
在 React 中,您在回调中收到的事件对象(即 SyntheticEvent
)是池化的(现在是 documented)。这意味着在事件回调被调用后,你收到的 SyntheticEvent 会以空属性放回池中,以减少 GC 压力。
因此,如果您以与原始回调异步的方式访问 SyntheticEvent
属性(如果您限制/去抖动,则可能会出现这种情况),您访问的属性可能会被删除。如果您希望事件永远不会被放回池中,您可以使用 persist()
方法。
没有持久化(默认行为:池化事件)
onClick = e => {
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
第二个(异步)将打印 hasNativeEvent=false
因为事件属性已被清理。
随着坚持
onClick = e => {
e.persist();
alert(`sync -> hasNativeEvent=${!!e.nativeEvent}`);
setTimeout(() => {
alert(`async -> hasNativeEvent=${!!e.nativeEvent}`);
}, 0);
};
第二个(异步)将打印 hasNativeEvent=true
,因为 persist
允许您避免将事件放回池中。
您可以在此处测试这两种行为:JsFiddle
阅读 Julen's answer,了解将 persist()
与节流/去抖动功能结合使用的示例。
不受控制的组件
您可以使用 event.persist()
method。
下面是一个使用下划线的 _.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} />
);
}
});
编辑:见this JSFiddle
受控组件
更新:上面的示例显示了一个 uncontrolled component。我一直使用受控元素,所以这是上面的另一个例子,但没有使用 event.persist()
“诡计”。
JSFiddle is available 也是如此。 Example without underscore
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 提出的问题
编辑:使用不使用下划线并使用纯 javascript debounce 的 jsfiddle 更新。
<input value={this.props.someprop}...
,那么它将无法正确呈现,因为按键上的更新直到去抖动之后才会返回到组件中。如果您对它不受管理感到高兴,可以省略 value=
,但如果您想预先填充该值和/或将其绑定到其他地方,那么显然这不起作用。
persist
,尤其是在可能有很多事件时,例如在 mousemove
上。我已经看到代码以这种方式变得完全没有响应。在事件调用中从本机事件中提取所需数据,然后仅使用数据而不是事件本身调用去抖动/节流函数会更有效。无需以这种方式坚持事件
2019:使用“useCallback”反应钩子
在尝试了许多不同的方法后,我发现使用 useCallback
是解决在 onChange
事件中使用 debounce
的多次调用问题最简单和最有效的方法。
useCallback 返回回调的记忆版本,仅当依赖项之一发生更改时才会更改。
将空数组作为依赖项传递可确保回调仅被调用一次。这是一个简单的实现:
import React, { useCallback } from "react";
import { debounce } from "lodash";
const handler = useCallback(debounce(someFunction, 2000), []);
const onChange = (event) => {
// perform any event related action here
handler();
};
希望这可以帮助!
debounce()
不认为 onChange()
回调是相同的回调方法吗?
const testFunc2 = useCallback(debounce((text) => console.log('testFunc2() has ran:', text), 1000) , []);
移动到函数组件的主体内,否则 React 会输出一条关于在其外部使用钩子的错误消息。然后在 onChange
事件处理程序中:<input type='text' name='name' className='th-input-container__input' onChange={evt => {testFunc2(evt.target.value);}}
。
在与文本输入斗争了一段时间后,我自己没有找到完美的解决方案,我在 npm 上找到了这个: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 组件接受您可以分配给普通输入元素的所有道具。在 codepen 上试用
我希望它也可以帮助其他人并节省他们一些时间。
我发现 Justin Tulk 的 this post 很有帮助。经过几次尝试,人们认为这是更正式的 react/redux 方式,它表明它由于 React's synthetic event pooling 而失败。然后,他的解决方案使用一些内部状态来跟踪输入中更改/输入的值,并在 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
,您需要使用 event.persist()
保留原始合成事件。这是使用 React 16+
测试的工作示例。
import React, { Component } from 'react';
import debounce from 'lodash/debounce'
class ItemType extends Component {
evntHandler = debounce((e) => {
console.log(e)
}, 500);
render() {
return (
<div className="form-field-wrap"
onClick={e => {
e.persist()
this.evntHandler(e)
}}>
...
</div>
);
}
}
export default ItemType;
使用功能组件,您可以做到这一点 -
const Search = ({ getBooks, query }) => {
const handleOnSubmit = (e) => {
e.preventDefault();
}
const debouncedGetBooks = debounce(query => {
getBooks(query);
}, 700);
const onInputChange = e => {
debouncedGetBooks(e.target.value)
}
return (
<div className="search-books">
<Form className="search-books--form" onSubmit={handleOnSubmit}>
<Form.Group controlId="formBasicEmail">
<Form.Control type="text" onChange={onInputChange} placeholder="Harry Potter" />
<Form.Text className="text-muted">
Search the world's most comprehensive index of full-text books.
</Form.Text>
</Form.Group>
<Button variant="primary" type="submit">
Search
</Button>
</Form>
</div>
)
}
参考文献 - - https://gist.github.com/elijahmanor/08fc6c8468c994c844213e4a4344a709 - https://blog.revathskumar.com/2016/02/reactjs-using-debounce-in-react-components.html
如果您需要从事件对象中获取 DOM 输入元素,则解决方案要简单得多 - 只需使用 ref
。请注意,这需要 Underscore:
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} />
}
}
我的解决方案是基于钩子的(用 Typescript 编写)。
我有 2 个主要钩子 useDebouncedValue
和 useDebouncedCallback
第一个 - useDebouncedValue
假设我们有一个搜索框,但我们想在用户停止输入 0.5 秒后向服务器询问搜索结果
function SearchInput() {
const [realTimeValue, setRealTimeValue] = useState('');
const debouncedValue = useDebouncedValue(realTimeValue, 500); // this value will pick real time value, but will change it's result only when it's seattled for 500ms
useEffect(() => {
// this effect will be called on seattled values
api.fetchSearchResults(debouncedValue);
}, [debouncedValue])
return <input onChange={event => setRealTimeValue(event.target.value)} />
}
执行
import { useState, useEffect } from "react";
export function useDebouncedValue<T>(input: T, time = 500) {
const [debouncedValue, setDebouncedValue] = useState(input);
// every time input value has changed - set interval before it's actually commited
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedValue(input);
}, time);
return () => {
clearTimeout(timeout);
};
}, [input, time]);
return debouncedValue;
}
第二个useDebouncedCallback
它只是在您的组件范围内创建一个“去抖动”功能。
假设我们有一个带有按钮的组件,该按钮会在您停止单击后 500 毫秒显示警报。
function AlertButton() {
function showAlert() {
alert('Clicking has seattled');
}
const debouncedShowAlert = useDebouncedCallback(showAlert, 500);
return <button onClick={debouncedShowAlert}>Click</button>
}
实现(注意我使用 lodash/debounce 作为助手)
import debounce from 'lodash/debounce';
import { useMemo } from 'react';
export function useDebouncedCallback<T extends (...args: any) => any>(callback: T, wait?: number) {
const debouncedCallback = useMemo(() => debounce(callback, wait), [callback, wait]);
return debouncedCallback;
}
可以有一个使用反应钩子的简单方法。
第 1 步:定义一个状态来维护搜索到的文本
const [searchTerm, setSearchTerm] = useState('')
第 2 步:使用 useEffect 捕捉搜索词的任何变化
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
if (searchTerm) {
// write your logic here
}
}, 400)
return () => clearTimeout(delayDebounceFn)
}, [searchTerm])
第 3 步:编写一个函数来处理输入变化
function handleInputChange(value) {
if (value) {
setSearchTerm(value)
}
}
就这样 !在需要时调用此方法
您可以将 use-debounce
包与 ReactJS 挂钩一起使用。
从包的自述文件中:
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>
);
}
从上面的示例可以看出,它设置为每秒仅更新一次变量 value
(1000 毫秒)。
这里已经有很多好的信息,但要简洁。这对我有用...
import React, {Component} from 'react';
import _ from 'lodash';
class MyComponent extends Component{
constructor(props){
super(props);
this.handleChange = _.debounce(this.handleChange.bind(this),700);
};
_debounce
包装它可以工作。不过我喜欢这个主意!
如果您使用的是 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)
}
}
然后,您可以为动作创建者添加去抖动功能,例如:
export default debouncedAction = (payload) => ({
type : 'DEBOUNCED_ACTION',
payload : payload,
meta : {debounce : 300}
}
实际上,already middleware 您可以从 npm 下车来为您执行此操作。
applyMiddleware(...)
链中执行的
使用 ES6 CLASS 和 React 15.xx 和 lodash.debounce 我在这里使用 React 的 refs,因为事件在内部丢失了 this 绑定。
类 UserInput 扩展 React.Component { 构造函数(props) { super(props); this.state = {用户输入:“”}; this.updateInput = _.debounce(this.updateInput, 500); } updateInput(userInput) { this.setState({ userInput }); //OrderActions.updateValue(userInput);//做一些服务器的事情 } render() { return (
User typed: { this.state.userInput }
this.updateInput(this.refs.userValue.value) } type = "text" />
您可以使用 Lodash debounce https://lodash.com/docs/4.17.5#debounce 方法。它简单有效。
import * as lodash from lodash;
const update = (input) => {
// Update the input here.
console.log(`Input ${input}`);
}
const debounceHandleUpdate = lodash.debounce((input) => update(input), 200, {maxWait: 200});
doHandleChange() {
debounceHandleUpdate(input);
}
您也可以使用以下方法取消去抖动方法。
this.debounceHandleUpdate.cancel();
希望它可以帮助你。干杯!!
供参考
这是另一个 PoC 实现:
没有任何用于去抖动的库(例如 lodash)
使用 React Hooks API
我希望它有帮助:)
import React, { useState, useEffect, ChangeEvent } from 'react';
export default function DebouncedSearchBox({
inputType,
handleSearch,
placeholder,
debounceInterval,
}: {
inputType?: string;
handleSearch: (q: string) => void;
placeholder: string;
debounceInterval: number;
}) {
const [query, setQuery] = useState<string>('');
const [timer, setTimer] = useState<NodeJS.Timer | undefined>();
useEffect(() => {
if (timer) {
clearTimeout(timer);
}
setTimer(setTimeout(() => {
handleSearch(query);
}, debounceInterval));
}, [query]);
const handleOnChange = (e: ChangeEvent<HTMLInputElement>): void => {
setQuery(e.target.value);
};
return (
<input
type={inputType || 'text'}
className="form-control"
placeholder={placeholder}
value={query}
onChange={handleOnChange}
/>
);
}
2019 年末,React 和 React Native 有了另一种解决方案:
<input>
<Debounce ms={500}>
<List/>
</Debounce>
它是一个组件,易于使用,小巧且支持广泛
例子:
https://i.stack.imgur.com/ptVlA.gif
import React from 'react';
import Debounce from 'react-debounce-component';
class App extends React.Component {
constructor (props) {
super(props);
this.state = {value: 'Hello'}
}
render () {
return (
<div>
<input value={this.state.value} onChange={(event) => {this.setState({value: event.target.value})}}/>
<Debounce ms={1000}>
<div>{this.state.value}</div>
</Debounce>
</div>
);
}
}
export default App;
*我是这个组件的创建者
我在这个问题下找不到任何答案,提到我正在使用的方法,所以只想在这里提供一个我认为最适合我的用例的替代解决方案。
如果您正在使用名为 react-use
的流行的 react hooks 工具包库,那么有一个名为 useDebounce()
的实用程序钩子以一种非常优雅的方式实现了谴责逻辑。
const [query, setQuery] = useState('');
useDebounce(
() => {
emitYourOnDebouncedSearchEvent(query);
},
2000,
[query]
);
return <input onChange={({ currentTarget }) => setQuery(currentTarget.value)} />
有关详细信息,请直接查看 lib 的 github 页面。
https://github.com/streamich/react-use/blob/master/docs/useDebounce.md
至于 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;
}
一个不错且干净的解决方案,不需要任何外部依赖项:
它使用自定义加上 useEffect React 钩子和 setTimeout
/ clearTimeout
方法。
2022 - 使用 useEffect 挂钩
此时最好的选择是使用 useEffect
挂钩。 useEffect
允许您设置一个函数,该函数可以修改状态以响应某些异步事件。去抖动是异步的,因此 useEffect 可以很好地用于此目的。
如果你从钩子返回一个函数,返回的函数将在再次调用钩子之前被调用。这使您可以取消先前的超时,从而有效地消除功能。
例子
这里我们有两个状态,value
和 tempValue
。设置 tempValue
将触发 useEffect
挂钩,该挂钩将启动 1000 毫秒超时,这将调用函数将 tempValue
复制到 value
。
该钩子返回一个取消设置计时器的函数。当再次调用钩子(即按下另一个键)时,超时被取消并重置。
const DebounceDemo = () => {
const [value, setValue] = useState();
const [tempValue, setTempValue] = useState();
// This hook will set a 1000 ms timer to copy tempValue into value
// If the hook is called again, the timer will be cancelled
// This creates a debounce
useEffect(
() => {
// Wait 1000ms before copying the value of tempValue into value;
const timeout = setTimeout(() => {
setValue(tempValue);
}, 1000);
// If the hook is called again, cancel the previous timeout
// This creates a debounce instead of a delay
return () => clearTimeout(timeout);
},
// Run the hook every time the user makes a keystroke
[tempValue]
)
// Here we create an input to set tempValue.
// value will be updated 1000 ms after the hook is last called,
// i.e after the last user keystroke.
return (
<>
<input
onChange={
({ target }) => setTempValue(target.value)
}
/>
<p>{ value }</p>
</>
)
}
只是最近 react 和 lodash 的另一个变体。
class Filter extends Component {
static propTypes = {
text: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
}
state = {
initialText: '',
text: ''
}
constructor (props) {
super(props)
this.setText = this.setText.bind(this)
this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
}
static getDerivedStateFromProps (nextProps, prevState) {
const { text } = nextProps
if (text !== prevState.initialText) {
return { initialText: text, text }
}
return null
}
setText (text) {
this.setState({ text })
this.onChange(text)
}
onChange (text) {
this.props.onChange(text)
}
render () {
return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
}
}
你试过了吗?
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) {
debounce(\\ Your handleChange code , 200);
}
});
与其将 handleOnChange 包装在 debounce() 中,不如将 ajax 调用包装在 debounce 内部的回调函数中,从而不破坏事件对象。所以是这样的:
handleOnChange: function (event) {
debounce(
$.ajax({})
, 250);
}
这是我想出的一个例子,它用去抖动器包装了另一个类。这很适合被制作成装饰器/高阶函数:
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} />
}
}
这是一个使用 @Abra 方法包装在功能组件中的片段(我们使用织物作为 UI,只需将其替换为一个简单的按钮)
import React, { useCallback } from "react";
import { debounce } from "lodash";
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
const debounceTimeInMS = 2000;
export const PrimaryButtonDebounced = (props) => {
const debouncedOnClick = debounce(props.onClick, debounceTimeInMS, { leading: true });
const clickHandlerDebounced = useCallback((e, value) => {
debouncedOnClick(e, value);
},[]);
const onClick = (e, value) => {
clickHandlerDebounced(e, value);
};
return (
<PrimaryButton {...props}
onClick={onClick}
/>
);
}
今天遇到了这个问题。使用 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
这个解决方案不需要任何额外的库,当用户按下回车键时它也会启动:
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} />
钩:
import {useState} from "react";
const useDebounce = ({defaultTimeout = 250, defaultIdentifier = 'default'} = {}) => {
const [identifiers, setIdentifiers] = useState({[defaultIdentifier]: null});
return ({fn = null, identifier = defaultIdentifier, timeout = defaultTimeout} = {}) => {
if (identifiers.hasOwnProperty(identifier)) clearTimeout(identifiers[identifier]);
setIdentifiers({...identifiers, [identifier]: setTimeout(fn, timeout)});
};
};
export default useDebounce;
并在任何地方使用它(在同一个文件中使用标识符来防止并发),例如:
const debounce = useDebounce();
const handlerA = () => {
debounce({fn: () => console.log('after 2000ms of last call with identifier A'), identifier: 'A', timeout: 2000});
};
const handlerB = () => {
debounce({fn: () => console.log('after 1500ms of last call with identifier B'), identifier: 'B', timeout: 1500});
};
简单有效
https://www.npmjs.com/package/use-debounce
use-debounce
import { useDebouncedCallback } from 'use-debounce';
function Input({ defaultValue }) {
const [value, setValue] = useState(defaultValue);
const debounced = useDebouncedCallback(
(value) => {
setValue(value);
},
// delay
1000
);
return (
<div>
<input defaultValue={defaultValue} onChange={(e) => debounced(e.target.value)} />
<p>Debounced value: {value}</p>
</div>
);
}
我正在寻找相同问题的解决方案,并且遇到了这个线程以及其他一些线程,但他们遇到了同样的问题:如果您尝试执行 handleOnChange
函数并且您需要来自事件目标的值,您将得到 cannot read property value of null
或一些这样的错误。在我的例子中,我还需要在 debounced 函数中保留 this
的上下文,因为我正在执行一个可变动作。这是我的解决方案,它适用于我的用例,所以我把它留在这里,以防有人遇到这个线程:
// 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)
不定期副业成功案例分享
handleOnChange = debounce((e) => { /* onChange handler code here */ }, timeout)
,而不是在构造函数中定义您的方法(感觉很奇怪)。您仍然有效地设置了一个实例成员,但它看起来更像是一个普通的方法定义。如果您还没有定义constructor
,则不需要constructor
。我想这主要是一种风格偏好。componentWillUnmount
:this.method.cancel()
中的 debounced 方法 - 否则它可能想要在未安装的组件上设置 setState。