React防抖需避免重复创建函数、确保状态新鲜、定时器可清理;推荐useCallback+useRef封装稳定防抖函数,或使用useDebounce(值驱动)及react-use的useDebounce。

防抖在 React 组件中不是“套个函数就行”,关键在于避免重复创建、确保状态新鲜、定时器可清理。直接在事件里调用 _.debounce(fn, 300)(value) 或每次渲染都新建防抖函数,会导致防抖失效——因为每个新函数都带着独立的定时器,旧的没被清除,新的又启动,结果是输入 abcd 触发四次请求。
用 useCallback + useRef 封装稳定防抖函数
这是最通用、面试和生产都推荐的方式,能解决闭包捕获旧 state、函数引用不稳等问题。
-
useRef存储最新回调函数,确保 setTimeout 里执行的是当前最新逻辑 -
useCallback缓存防抖后的函数,依赖只在 callback 或 delay 变化时更新 - 每次调用先
clearTimeout,再设新定时器,保证只有最后一次生效
import { useCallback, useRef } from 'react';
function useDebounce<t extends any> any>(
callback: T,
delay: number
) {
const timeoutRef = useRef<nodejs.timeout null>(null);
const callbackRef = useRef(callback);
callbackRef.current = callback; // 始终指向最新 callback
return useCallback((...args: Parameters<t>) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callbackRef.current(...args);
}, delay);
}, [delay]);
}</t></nodejs.timeout></t>
使用示例:
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
const handleSearch = useDebounce((query: string) => {
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, 400);
<input onchange="{e"> handleSearch(e.target.value)}
placeholder="搜索..."
/>
用 useDebounce Hook(基于值的状态驱动)
适合「等某个值稳定后再触发副作用」的场景,比如搜索关键词变化后延迟发起请求,但不关心中间过程。
- 本质是
useState + useEffect的组合:监听value变化,延迟更新debouncedValue -
useEffect的清理函数自动清除上一个定时器 - 返回的是防抖后的值,不是函数,适合配合
useEffect做后续处理
import { useState, useEffect } from 'react';
function useDebounce<t>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<t>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}</t></t>
使用示例:
const [searchTerm, setSearchTerm] = useState('');
const debouncedTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedTerm) {
fetch(`/api/search?q=${debouncedTerm}`)
.then(r => r.json())
.then(setResults);
}
}, [debouncedTerm]);
用 react-use 的 useDebounce(开箱即用)
如果你项目已引入 `react-use`,它提供更丰富的控制能力:
- 返回
[isReady, cancel],可主动查询是否就绪、手动取消 -
deps参数语义同useEffect,防抖重置时机由依赖数组决定 - 内部已处理 ref 和清理,无需手写
npm install react-use
import { useDebounce } from 'react-use';
const [isReady, cancel] = useDebounce(
() => {
console.log('执行搜索', searchTerm);
},
400,
[searchTerm] // searchTerm 变,计时重来
);
// isReady() 返回 true 表示已到延迟时间且未被取消
// cancel() 可随时中断当前防抖流程
避开常见坑
- ❌ 不要在 `onChange` 里直接写 `_.debounce(handle, 300)(val)` —— 每次都新建实例 - ❌ 不要用 `useMemo(() => debounce(fn), [])` —— 闭包捕获初始 `fn`,永远拿不到新 state - ❌ 不要忽略 `useCallback` 依赖项 —— `callback` 和 `delay` 必须进依赖数组,否则更新后仍用旧逻辑 - ✅ 所有定时器必须可清除,且清除动作发生在新定时器启动前 - ✅ 异步操作(如 API 调用)建议加 loading 状态或 abort controller,防止竞态
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










