一、先给面试官的"标准定义"(先声夺人)
防抖(debounce) :在事件触发一段时间后才执行,期间再次触发会重新计时
节流(throttle):在固定时间内只执行一次
这一句一定要先说。
二、为什么在 React Hooks 里要"特别处理"?
问题本质:闭包 + 重新渲染
const fn = () => {
console.log(count)
}
-
每次 render 都是新函数
-
定时器 / 事件里用的可能是旧状态
👉 普通 JS 防抖在 Hooks 里会失效或拿到旧值
三、Hooks 防抖(推荐实现)
1️⃣ 最稳方案:useRef + useCallback
function useDebounceFn(fn, delay = 300) {
const timer = useRef<number | null>(null)
const fnRef = useRef(fn)
// 始终指向最新 fn,解决闭包问题
fnRef.current = fn
const debounce = useCallback((...args) => {
if (timer.current) {
clearTimeout(timer.current)
}
timer.current = window.setTimeout(() => {
fnRef.current(...args)
}, delay)
}, [delay])
return debounce
}
使用方式
const onSearch = useDebounceFn(value => {
fetchList(value)
}, 500)
<input onChange={e => onSearch(e.target.value)} />
2️⃣ 面试官加分点
使用 useRef 保存最新函数,避免闭包导致状态不更新。
四、Hooks 节流(两种方式都要会)
方案一:时间戳节流(最稳定)
function useThrottleFn(fn, delay = 300) {
const lastTime = useRef(0)
const fnRef = useRef(fn)
fnRef.current = fn
return useCallback((...args) => {
const now = Date.now()
if (now - lastTime.current >= delay) {
lastTime.current = now
fnRef.current(...args)
}
}, [delay])
}
方案二:定时器节流(常被追问)
function useThrottleFn(fn, delay = 300) {
const timer = useRef<number | null>(null)
const fnRef = useRef(fn)
fnRef.current = fn
return useCallback((...args) => {
if (timer.current) return
timer.current = window.setTimeout(() => {
fnRef.current(...args)
timer.current = null
}, delay)
}, [delay])
}
两种节流的区别(面试官爱问)
| 方式 | 特点 |
|---|---|
| 时间戳 | 立即执行 |
| 定时器 | 延迟执行 |
五、为什么不用 lodash debounce?
可以用,但要说清楚限制
const debouncedFn = useMemo(
() => debounce(fn, 300),
[]
)
❌ 问题:
-
fn 变了,debounce 不更新
-
容易产生脏数据
正确用法
const fnRef = useRef(fn)
fnRef.current = fn
const debounced = useMemo(
() => debounce((...args) => fnRef.current(...args), 300),
[]
)
六、Hooks 防抖节流常见坑(必背)
❌ 错误写法
const fn = debounce(() => {
setCount(count + 1)
}, 300)
问题
-
每次 render 都创建新 debounce
-
count 永远是旧值
✅ 正确认知
-
防抖节流函数要只创建一次
-
执行的函数要始终是最新的
七、什么时候用防抖?什么时候用节流?
防抖(Debounce)
-
搜索框
-
输入联想
-
表单校验
节流(Throttle)
-
滚动
-
resize
-
拖拽
八、面试终极总结(一定要背)
React Hooks 下实现防抖节流,关键不是 setTimeout,而是用 useRef 解决闭包问题,保证函数引用稳定且状态最新。