用 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 生成的内容可能有误,请核实重要信息。