先来看一段代码:
import debounce from 'lodash/debounce';
export default class Search extends Component {
constructor(props) {
super(props)
this.handleSearch = debounce(this.handleSearch, 500);
}
handleSubmit = (e) => {
e.preventDefault();
this.handleSearch();
}
handleSearch = () => {
...
}
render() {
return (
<form onSubmit={this.handleSubmit}><form>
)
}
}
这是一段在react类组件中使用节流函数的示例代码,而当你想在函数式组件中使用节流函数时,你可能会自然而然的想要这么做::
import React, {useState} from "react";
import debounce from 'lodash/debounce';
const sendQuery = (query) => console.log(`Querying for ${query}`);
const Search = () => {
const [userQuery, setUserQuery] = useState("");
const delayedQuery = debounce(q => sendQuery(q), 500);
const onChange = e => {
setUserQuery(e.target.value);
delayedQuery(e.target.value);
};
return (
<div>
<label>Search:</label>
<input onChange={onChange} value={userQuery} />
</div>
);
}
但这样子做防抖函数并没有发挥作用,仅仅是把反应时间延后了500毫秒。这是因为函数时组件每次渲染结束之后,内部的变量都会被释放,重新渲染时所有的变量会被重新初始化,产生的结果就是每一次都注册和执行了setTimeout函数。想要得到正确的运行结果,必须以某种方式存储那些本会被删除的变量和方法的引用。很遗憾,没办法直接使用useState去存储,但是我们可以直接构造一个新的hook去解决这个问题。
import React, { useState, useEffect } from 'react';
// Our hook
export default function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Set debouncedValue to value (passed in) after the specified delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Return a cleanup function that will be called every time ...
// ... useEffect is re-called. useEffect will only be re-called ...
// ... if value changes (see the inputs array below).
// This is how we prevent debouncedValue from changing if value is ...
// ... changed within the delay period. Timeout gets cleared and restarted.
// To put it in context, if the user is typing within our app's ...
// ... search box, we don't want the debouncedValue to update until ...
// ... they've stopped typing for more than 500ms.
return () => {
clearTimeout(handler);
};
},
// Only re-call effect if value changes
// You could also add the "delay" var to inputs array if you ...
// ... need to be able to change that dynamically.
[value]
);
return debouncedValue;
}
构造完useDebounce函数后,我们可以这样使用:
import React, { useState, useEffect } from 'react';
import useDebounce from './use-debounce';
const sendQuery = (query) => console.log(`Querying for ${query}`);
// Usage
function Search() {
// State and setter for search term
const [searchTerm, setSearchTerm] = useState('');
// State and setter for search results
const [results, setResults] = useState([]);
// State for search status (whether there is a pending API request)
const [isSearching, setIsSearching] = useState(false);
// Now we call our hook, passing in the current searchTerm value.
// The hook will only return the latest value (what we passed in) ...
// ... if it's been more than 500ms since it was last called.
// Otherwise, it will return the previous value of searchTerm.
// 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);
// Here's where the API call happens
// We use useEffect since this is an asynchronous action
useEffect(
() => {
// Make sure we have a value (user has entered something in input)
if (debouncedSearchTerm) {
// Set isSearching state
setIsSearching(true);
// Fire off our API call
sendQuery(debouncedSearchTerm);
// Set back to false since request finished
setIsSearching(false);
// Set results state
setResults(results);
} else {
setResults([]);
}
},
// This is the useEffect input array
// Our useEffect function will only execute if this value changes ...
// ... and thanks to our hook it will only change if the original ...
// value (searchTerm) hasn't changed for more than 500ms.
[debouncedSearchTerm]
);
return (
<div>
<label>Search:</label>
<input onChange={e => setSearchTerm(e.target.value) />
</div>
);
}
上面的方法还是太复杂了,通过使用useRef和useCallback我们可以更简单的去解决这个问题:
const SearchFixed = () => {
const [userQuery, setUserQuery] = useState("");
const delayedQuery = useRef(debounce(q => sendQuery(q), 500)).current;
const onChange = e => {
setUserQuery(e.target.value);
delayedQuery(e.target.value);
};
return (
<div>
<label>Search Fixed:</label>
<input onChange={onChange} value={userQuery} />
</div>
);
};
或是:
const SearchFixed = () => {
const [userQuery, setUserQuery] = useState("");
const delayedQuery = useCallback(debounce(q => sendQuery(q), 500), []);
const onChange = e => {
setUserQuery(e.target.value);
delayedQuery(e.target.value);
};
return (
<div>
<label>Search Fixed:</label>
<input onChange={onChange} value={userQuery} />
</div>
);
};
参考资料: