用 debounce 优化搜索框请求
搜索框每输入一个字就发一次请求,接口压力很大,有什么办法?
常见做法是给输入事件加 防抖(debounce):用户停止输入一段时间(比如 300ms)后才真正发请求。这样连续输入「react hooks」只会发一次,而不是 11 次。
另外两个可以配合的手段:
- 输入为空或与上次相同时不发请求;
- 新请求发出时取消上一个未完成的请求,避免旧结果覆盖新结果。
能给个不依赖 lodash 的实现吗?最好把取消请求也带上。
可以。下面这个版本只用原生 API:setTimeout 做防抖,AbortController 取消上一次请求。
javascript
function debounce(fn, wait = 300) {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), wait);
};
}
let controller = null;
async function search(keyword) {
const q = keyword.trim();
if (!q) return;
controller?.abort(); // 取消上一次还没回来的请求
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: controller.signal });
renderResults(await res.json());
} catch (err) {
if (err.name !== 'AbortError') showError(err);
}
}
input.addEventListener('input', debounce((e) => search(e.target.value), 300));
几个要点:
AbortError是主动取消产生的,不应该当作错误提示给用户;- 300ms 是经验值,移动端输入较慢,可以放宽到 400–500ms;
- 如果结果列表需要「输入即出」的体感,可以先展示本地缓存的结果,再用接口结果替换。
关于 AbortController 的浏览器兼容性和更多用法,可以参考 MDN 文档:https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort?utm_source=assistant&utm_medium=chat&utm_campaign=debounce-search-optimization-reference-link-example#examples_aborting_a_fetch_request_with_a_signal
那 debounce 和 throttle 到底怎么选?
看你关心的是「最后一次」还是「过程中的节奏」:
- debounce:只在停下来之后执行一次。适合搜索联想、表单校验、窗口 resize 结束后重新布局。
- throttle:固定间隔最多执行一次。适合滚动监听、拖拽、上报埋点这类需要持续反馈的场景。
搜索框用 debounce 就够了。
好的,最后一个问题:React 里怎么写才不会每次渲染都重新创建 debounce 函数?
把防抖后的函数用 useMemo 或 useRef 保存起来,让它在组件生命周期内只创建一次;同时在组件卸载时清掉定时器并 abort 未完成的请求,避免卸载后还去 setState。
如果项目里已经有类似 useDebouncedCallback 的 hook,直接复用它最省事。
明白了,谢谢!
不客气。上线后可以对比一下接口 QPS,防抖一般能让搜索请求量下降一个数量级。
Enter 发送,Shift + Enter 换行。AI 生成的内容可能有误,请核实重要信息。