React useDebounce输入防抖失效 接口频繁触发问题排查
问题描述
使用名为Combobox的输入组件时,输入字符串过程中会持续触发查询请求。已编写自定义防抖hook,预期实现输入停止500ms后再执行查询的效果,但实际功能未生效,输入时仍会持续发请求。
自定义防抖Hook代码
import { useEffect, useState } from 'react' function useDebounce<T>(value: T, delay?: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value) useEffect(() => { const timer = setTimeout(() => setDebouncedValue(value), delay || 500) return () => { clearTimeout(timer) } }, [value, delay]) return debouncedValue } export default useDebounce
注:上述防抖Hook实现逻辑本身没有问题
业务组件原始代码
export default function TagSelectionInput() { const [query, setQuery] = useState(''); const debouncedValue = useDebounce(query, 500); const [filterTags, { data, loading, error }] = useLazyQuery<FilterTagsQuery>( filterQuery, { variables: { filter: query, offset: 0, limit: 20, }, }, ); useEffect(() => { const fetchTags = async () => { if (query.length > 0) { const data = await filterTags(); console.log(data); } }; fetchTags(); }, [data, debouncedValue, filterTags]); return ( <Combobox value={query} onChange={setQuery}> <div className="relative"> <SearchIcon className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-400" aria-hidden="true" /> <Combobox.Input className="border border-gray-300 rounded-md shadow-sm h-10 w-full focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm pl-11 pr-4 text-gray-800 placeholder-gray-400" placeholder="Search..." onChange={(event) => setQuery(event.target.value)} /> </div> {/* 其余组件内容 */} </Combobox> ) }
问题根因
防抖失效、请求重复触发的问题全部出在业务组件的逻辑编写上,共有4处错误:
- useEffect依赖项配置错误:将
data加入了依赖数组,每次查询返回新数据后,会触发effect重新执行,形成额外的重复请求。 - 逻辑判断未使用防抖值:触发请求的长度判断用的是实时更新的原始
query值,而非防抖后的debouncedValue,输入过程中只要原始输入长度大于0就会进入请求逻辑。 - 请求参数未使用防抖值:初始化
useLazyQuery时绑定的variables取的是实时query,且调用filterTags时未动态传参,就算触发时机正确,请求携带的也是实时输入内容,达不到防抖效果。 - 冗余依赖项:
filterTags是useLazyQuery返回的稳定方法引用,不需要加入依赖数组,多余的依赖可能引发不必要的effect执行。
修复方案
调整业务组件的请求逻辑,所有触发判断、请求参数统一使用防抖后的值,修正useEffect依赖项:
export default function TagSelectionInput() { const [query, setQuery] = useState(''); const debouncedValue = useDebounce(query, 500); // 移除初始化时写死的variables配置 const [filterTags, { data, loading, error }] = useLazyQuery<FilterTagsQuery>( filterQuery ); useEffect(() => { const fetchTags = async () => { // 用防抖后的值做长度判断 if (debouncedValue.length > 0) { // 调用请求方法时动态传入防抖后的参数 const res = await filterTags({ variables: { filter: debouncedValue, offset: 0, limit: 20, } }); console.log(res); } }; fetchTags(); // 只保留防抖值作为唯一依赖,移除data、filterTags等冗余/错误依赖 }, [debouncedValue]); return ( <Combobox value={query} onChange={setQuery}> <div className="relative"> <SearchIcon className="pointer-events-none absolute top-3.5 left-4 h-5 w-5 text-gray-400" aria-hidden="true" /> <Combobox.Input className="border border-gray-300 rounded-md shadow-sm h-10 w-full focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm pl-11 pr-4 text-gray-800 placeholder-gray-400" placeholder="Search..." onChange={(event) => setQuery(event.target.value)} /> </div> {/* 其余原有组件内容 */} </Combobox> ) }
内容的提问来源于stack exchange,提问作者jonduzte
相关产品推荐
相关产品推荐

