创见博客
手写实现useDebounce以及useDebounceFn
七崽爱吃小饼干2026/02/25阅读 2专栏 React

实现思路

防抖(Debounce)的核心逻辑是:延迟执行目标函数,若在延迟期间函数再次被触发,则重置延迟时间,直到延迟结束后才执行。 实现 useDebounce Hook 需包含以下关键点:

  1. 使用 useRef 保存定时器 ID,避免每次渲染丢失;
  2. 接收目标值/函数和延迟时间作为参数;
  3. 清理上一次的定时器,确保只执行最后一次触发的逻辑;
  4. 组件卸载时清除定时器,避免内存泄漏。

完整实现代码

版本1:防抖值(最常用,如输入框防抖)

适用于需要对「值」进行防抖的场景(比如搜索框输入后延迟更新搜索关键词):

jsx
import { useState, useEffect, useRef } from 'react';

/**
 * 防抖 Hook(针对值)
 * @param {any} value - 需要防抖的目标值
 * @param {number} delay - 防抖延迟时间(毫秒),默认500ms
 * @returns {any} 防抖后的最终值
 */
function useDebounce(value, delay = 500) {
  // 保存防抖后的最终值
  const [debouncedValue, setDebouncedValue] = useState(value);
  // 保存定时器ID,避免每次渲染重置
  const timerRef = useRef(null);

  useEffect(() => {
    // 每次value变化时,先清除上一次的定时器
    clearTimeout(timerRef.current);
    // 重新设置定时器,延迟更新防抖值
    timerRef.current = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // 组件卸载/依赖变化时清除定时器(清理副作用)
    return () => {
      clearTimeout(timerRef.current);
    };
  }, [value, delay]); // 依赖:目标值 + 延迟时间

  return debouncedValue;
}

// 用法示例:搜索框防抖
function SearchInput() {
  const [inputValue, setInputValue] = useState('');
  // 获取防抖后的输入值(延迟500ms更新)
  const debouncedSearchValue = useDebounce(inputValue, 500);

  // 监听防抖值变化,执行搜索逻辑(仅在输入停止500ms后触发)
  useEffect(() => {
    if (debouncedSearchValue) {
      console.log('执行搜索:', debouncedSearchValue);
      // 这里可调用接口请求搜索数据
    }
  }, [debouncedSearchValue]);

  return (
    <input
      type="text"
      value={inputValue}
      onChange={(e) => setInputValue(e.target.value)}
      placeholder="输入关键词搜索(防抖500ms)"
    />
  );
}

export default SearchInput;

版本2:防抖函数(针对方法)

适用于需要对「函数执行」进行防抖的场景(比如按钮点击、窗口resize):

jsx
import { useCallback, useRef } from 'react';

/**
 * 防抖 Hook(针对函数)
 * @param {Function} fn - 需要防抖的目标函数
 * @param {number} delay - 防抖延迟时间(毫秒),默认500ms
 * @returns {Function} 防抖后的函数
 */
function useDebounceFn(fn, delay = 500) {
  // 保存定时器ID
  const timerRef = useRef(null);

  // 使用useCallback缓存函数,避免每次渲染返回新函数
  const debouncedFn = useCallback(
    (...args) => {
      // 清除上一次的定时器
      if (timerRef.current) {
        clearTimeout(timerRef.current);
      }
      // 重置定时器,延迟执行目标函数
      timerRef.current = setTimeout(() => {
        fn(...args); // 传递参数给目标函数
      }, delay);
    },
    [fn, delay]
  );

  // 提供清除定时器的方法(可选,比如手动取消防抖)
  const cancel = useCallback(() => {
    clearTimeout(timerRef.current);
    timerRef.current = null;
  }, []);

  return { debouncedFn, cancel };
}

// 用法示例:按钮点击防抖
function DebounceButton() {
  // 定义目标函数
  const handleClick = (msg) => {
    console.log('按钮点击触发:', msg);
    // 这里可执行提交表单、请求接口等逻辑
  };

  // 获取防抖后的函数
  const { debouncedFn: debouncedClick } = useDebounceFn(handleClick, 800);

  return (
    <button onClick={() => debouncedClick('测试防抖')}>
      点击触发(防抖800ms)
    </button>
  );
}

代码关键解释

  1. useRef 保存定时器ID:timerRef 不会因组件重新渲染而重置,确保能准确清除上一次的定时器;
  2. useEffect 处理副作用:在值变化时触发防抖逻辑,组件卸载时清理定时器,避免内存泄漏;
  3. useCallback 缓存函数:针对防抖函数版本,避免每次渲染返回新的函数(比如传递给子组件时,防止子组件不必要的重渲染);
  4. 参数灵活性:两个版本都支持自定义延迟时间,函数版本还支持传递参数给目标函数。

总结

  1. useDebounce 分两种核心场景:防抖值(输入框)和 防抖函数(按钮/事件),可根据需求选择;
  2. 核心逻辑是「清除旧定时器 + 重置新定时器」,确保只有最后一次触发的逻辑会执行;
  3. 必须在组件卸载时清除定时器,避免内存泄漏(通过 useEffect 返回清理函数/手动 cancel 方法)。
评论
0/100