← 中文资源

简体中文版本

搜索输入防抖且不丢按键不丢请求

对昂贵副作用防抖而不是对输入值防抖,新输入到达时取消在途请求,并保持每个按键即时响应。

阅读英文原文

目标

经典 typeahead bug 是防抖了错误的对象:输入值被延迟,按键像被吃掉,请求照样风暴。正确做法是只对昂贵的副作用(fetch)防抖,输入值同步更新,并取消一切被取代的东西——计时器与在途请求双双清场,再加一道请求 id 保险带,打字与网络同时安静下来。

步骤要点

  1. 状态分两个:query 每次按键同步更新保证输入即时回显,results 只在防抖副作用里写入。
  2. effect 里 setTimeout 300ms 后发请求,cleanup 同时 clearTimeout 与 AbortController.abort,新按键到来时未发的与在途的一起取消。
  3. 加单调递增请求 id 守卫:响应回来时若不是最新 id 就丢弃,即使某些环境不支持 abort 也不会被旧结果覆盖。
  4. 语义定为尾随触发:搜索在停顿之后发出而非首键即发,清空输入则完全绕过防抖立即复位。
  5. 用 Playwright 脚本验证爆发输入:80ms 一键连打 “checkout” 应恰好产生一个请求,两段式输入恰好两个请求。

排错提示

输入本身发卡是把防抖包在了状态更新上而不是副作用上,把受控值改成同步更新、只让 fetch 等待;响应仍互相覆盖说明取消不完整,cleanup 里要同时清计时器和 abort 请求,再留 id 守卫给不支持 AbortController 的传输;服务端连防抖后的量都扛不住就把延迟提到 350-500ms、设最小两到三字符、并对相同查询做缓存让重复输入零成本。

适用边界

防抖是拿“逐键即时搜索”换平静,产品确实要逐字即搜就得靠服务端限流;300ms 是默认值不是法律,量测你的用户打字节奏再调,且任何延迟下取消路径都必须严丝合缝。

代码示例

before (debounce on state):
  keystrokes: c-h-e-c-k-o-u-t
  input echo: lags 300ms behind, chars "lost" visually
  requests: 8 (one per char), out-of-order overwrites

after (debounce on effect + abort):
  keystrokes: c-h-e-c-k-o-u-t (80ms apart)
  input echo: instant, every char visible
  requests: 1, q=checkout, fired 300ms after last key
  aborts: 7 timers cleared, 0 in-flight (none sent)

burst test (Playwright):
  fill("checkout") -> expect requests == ["checkout"]