使用反应钩子去抖动(在几秒钟内仅触发一次功能)
Debouncing (fire function only once in few second) with react hooks
我需要在用户输入上实现标签搜索,但输入速度很快,我不想为用户输入的每个符号触发数据库调用,所以我很好奇,有没有一种简单的去抖动的好方法api 调用比方说 - 延迟 3 秒后调用一次?
现在我想出了这个:
let searchDelay
async function handleTagSearch(e) {
clearTimeout(searchDelay)
tagSearchPhraseSet(e.target.value)
searchDelay = setTimeout(async () => {
if (e.target.value.length > 3) {
let res = await fetch('api/tag_seatch/' + e.target.value)
res = await res.json()
console.log(res)
}
}, 3000)
}
但这是正确的做法吗?
您需要考虑的第一件事是使用 useCallback
进行记忆,如果您只是编写一个普通函数,它将在每次重新渲染时重新实例化。 IMO 你应该使用 lodash 的 debounce 函数而不是实现你自己的。结果看起来像这样:
const searchTags = useCallback(debounce(async evt => {
const { value } = evt.target;
if(value.length > 3){
const response = await fetch('/api/tag_search', value);
const result = await response.json();
setTags(result) //or somewhere in your state
}
}, 3000, { trailing: true, leading: false }));
如果您确保 searchDelay
数字在渲染之间保持不变,例如一个 useRef
钩子。
另一种解决方法是在每次输入 value
更改时使用 运行 的 useEffect
挂钩。从提供给 useEffect
的函数中,您可以 return 一个清除上次超时的函数 运行。
例子
const { useState, useEffect } = React;
function App() {
const [value, setValue] = useState("");
const [result, setResult] = useState(null);
useEffect(
() => {
if (value.length < 3) {
setResult(null);
return;
}
const timeout = setTimeout(() => {
setResult(Math.random());
}, 3000);
return () => clearTimeout(timeout);
},
[value]
);
return (
<div>
<input value={value} onChange={e => setValue(e.target.value)} />
<div>{result}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
多亏了@Tholle 示例,我理解了 qoute "The function returned from the useEffect function will be invoked every time it is run again
, and on unmount as you say" 并提出了这个解决方案:
import React, { useState, useContext, useEffect, useRef } from 'react'
export function TagsAdd() {
const [searchTerm, searchTermSet] = useState('')
const isFirstRun = useRef(true)
useEffect(() => {
//skip first run on component mount
if (isFirstRun.current) {
isFirstRun.current = false
return
}
const timeout = setTimeout(() => {
tagSearch(searchTerm)
}, 2000) //2000 - timeout to execute this function if timeout will be not cleared
return () => clearTimeout(timeout) //clear timeout (delete function execution)
}, [searchTerm])
// API call only one time in 2 seconds for the last value! Yeeeee
async function tagSearch(value) {
let res = await fetch('api/tag_seatch/' + value)
res = await res.json()
console.log(res)
}
//handle input change
function handleInput(e) {
searchTermSet(e.target.value)
}
return (
<div>
<input value={searchTerm} onChange={handleInput} />
</div>
)
}
我需要在用户输入上实现标签搜索,但输入速度很快,我不想为用户输入的每个符号触发数据库调用,所以我很好奇,有没有一种简单的去抖动的好方法api 调用比方说 - 延迟 3 秒后调用一次?
现在我想出了这个:
let searchDelay
async function handleTagSearch(e) {
clearTimeout(searchDelay)
tagSearchPhraseSet(e.target.value)
searchDelay = setTimeout(async () => {
if (e.target.value.length > 3) {
let res = await fetch('api/tag_seatch/' + e.target.value)
res = await res.json()
console.log(res)
}
}, 3000)
}
但这是正确的做法吗?
您需要考虑的第一件事是使用 useCallback
进行记忆,如果您只是编写一个普通函数,它将在每次重新渲染时重新实例化。 IMO 你应该使用 lodash 的 debounce 函数而不是实现你自己的。结果看起来像这样:
const searchTags = useCallback(debounce(async evt => {
const { value } = evt.target;
if(value.length > 3){
const response = await fetch('/api/tag_search', value);
const result = await response.json();
setTags(result) //or somewhere in your state
}
}, 3000, { trailing: true, leading: false }));
如果您确保 searchDelay
数字在渲染之间保持不变,例如一个 useRef
钩子。
另一种解决方法是在每次输入 value
更改时使用 运行 的 useEffect
挂钩。从提供给 useEffect
的函数中,您可以 return 一个清除上次超时的函数 运行。
例子
const { useState, useEffect } = React;
function App() {
const [value, setValue] = useState("");
const [result, setResult] = useState(null);
useEffect(
() => {
if (value.length < 3) {
setResult(null);
return;
}
const timeout = setTimeout(() => {
setResult(Math.random());
}, 3000);
return () => clearTimeout(timeout);
},
[value]
);
return (
<div>
<input value={value} onChange={e => setValue(e.target.value)} />
<div>{result}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
多亏了@Tholle 示例,我理解了 qoute "The function returned from the useEffect function will be invoked every time it is run again
, and on unmount as you say" 并提出了这个解决方案:
import React, { useState, useContext, useEffect, useRef } from 'react'
export function TagsAdd() {
const [searchTerm, searchTermSet] = useState('')
const isFirstRun = useRef(true)
useEffect(() => {
//skip first run on component mount
if (isFirstRun.current) {
isFirstRun.current = false
return
}
const timeout = setTimeout(() => {
tagSearch(searchTerm)
}, 2000) //2000 - timeout to execute this function if timeout will be not cleared
return () => clearTimeout(timeout) //clear timeout (delete function execution)
}, [searchTerm])
// API call only one time in 2 seconds for the last value! Yeeeee
async function tagSearch(value) {
let res = await fetch('api/tag_seatch/' + value)
res = await res.json()
console.log(res)
}
//handle input change
function handleInput(e) {
searchTermSet(e.target.value)
}
return (
<div>
<input value={searchTerm} onChange={handleInput} />
</div>
)
}