你是否遇到过这样的场景:你在搜索框里想搜一个 "Apple",结果刚敲了几个字母,控制台的 Network 面板就像过年放鞭炮一样噼里啪啦闪个不停?
- 输入
A-> 发起请求 - 输入
Ap-> 发起请求 - 输入
App-> 发起请求 - ...
这不仅浪费了服务器资源,还可能导致竞态问题(先发出的请求后返回,覆盖了最新的结果),让用户看到的界面疯狂闪烁。
今天,我们不依赖臃肿的第三方库(如 lodash),只用原生的 React Hooks,手写一个轻量级、高性能的 useDebounce(防抖钩子),并深入剖析它背后的"电梯门哲学"。
核心代码:仅需 10 行
这就是我们要实现的 useDebounce。它的作用很简单:延迟值的更新。
JavaScript
import * as React from "react";
export default function useDebounce(value, delay) {
// 1. 保存一个内部状态,用于"滞后"更新
const [state, setState] = React.useState(value);
React.useEffect(() => {
// 2. 设定一个定时器:delay 毫秒后,才去更新 state
const id = window.setTimeout(() => {
setState(value);
}, delay);
// 3. 【关键的一步】清理函数
// 如果在 delay 时间还没到,value 就变了(用户又打字了),
// 这一行代码会立刻执行,杀掉上一个定时器。
return () => {
window.clearTimeout(id);
};
}, [value, delay]); // 依赖项:只要 value 或 delay 变了,就重置计时
return state;
}
它是如何工作的?(电梯门理论)
要理解这段代码,不要把它看作程序,把它想象成一部电梯的自动门。
delay(延迟时间) :电梯设定为"没人经过 5 秒后自动关门"。value改变 (用户输入) :有人走进电梯。clearTimeout(清理函数) :电梯的红外感应器。
剧本如下:
-
用户输入 "A" :
- 一个人走进电梯。
- 电梯开始倒计时:5... 4... 3...
-
用户迅速输入 "B" (此时倒计时刚走到 3):
- 感应器触发(Cleanup) :电梯检测到又有人来了,之前的倒计时作废!
- 重新计时:5... 4... 3...
-
用户输入 "C" :
- 感应器再次触发:之前的倒计时又作废。
- 重新计时:5... 4... 3... 2... 1... 0!
-
时间到:
- 没人再进来了,电梯关门(
setState执行)。 - 电梯上行(发起 API 请求)。
- 没人再进来了,电梯关门(
结论: 无论中间进了多少人,电梯只会在最后一个人进入并站稳后,才关门运行一次。这就是防抖。
最佳实践:UI 与 逻辑的分离
在实际组件中,我们需要维护两个"世界":
- 快世界 (
searchTerm) :响应用户打字,用于<input>的显示,必须实时,否则用户会觉得卡顿。 - 慢世界 (
debouncedTerm) :响应 API 请求,通过useDebounce过滤,只有静止时才更新。
JavaScript
import React, { useState, useEffect } from "react";
import useDebounce from "./useDebounce";
export default function SearchBar() {
// 1. 快状态:控制 UI,打字丝般顺滑
const [searchTerm, setSearchTerm] = useState("");
// 2. 慢状态:控制逻辑,延迟 500ms 更新
const debouncedTerm = useDebounce(searchTerm, 500);
// 3. 只有当"慢状态"改变时,才发请求
useEffect(() => {
if (debouncedTerm) {
console.log(`正在向服务器搜索: ${debouncedTerm}`);
// API.search(debouncedTerm)...
}
}, [debouncedTerm]); // <--- 这里的依赖是关键!
return (
<div className="p-4">
<input
type="text"
placeholder="输入关键词..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="border p-2 rounded w-full"
/>
<p className="text-gray-500 mt-2">
实时输入: {searchTerm} <br/>
最终搜索: {debouncedTerm}
</p>
</div>
);
}
总结
useEffect 不仅仅是生命周期的替代品,它的清理函数 (Cleanup Function) 才是处理异步逻辑的灵魂。
通过 setTimeout 延迟执行,配合 clearTimeout 在新变化到来时"毁约",我们仅用了几行原生代码就实现了高性能的防抖逻辑。
记住: 想要用户体验好,让 UI 跑得快一点;想要服务器压力小,让逻辑跑得慢一点。useDebounce 就是连接快慢世界的桥梁。