React 自定义 Hook 工程化:把 useDebounce、useThrottle 与 useLocalStorage 写成可复用资产

test12026-09-179 次阅读

为什么要把逻辑抽成自定义 Hook

组件里塞满防抖、节流、本地存储读写,会让业务组件越来越胖,且这些逻辑在多个页面反复出现。把它们收敛成自定义 Hook,组件只关心"何时调用、拿到什么值",测试与复用都更轻松。

useDebounce:高频输入只取最后一次

搜索框、自动保存这类场景,需要在用户停止操作一段时间后才触发。用 setTimeout 实现,并在依赖变化或卸载时清理。

import { useEffect, useState } from "react";

export function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debounced;
}

// 用法
const [text, setText] = useState("");
const keyword = useDebounce(text, 400);
useEffect(() => {
  if (keyword) fetch(`/api/search?q=${keyword}`);
}, [keyword]);

useThrottle:限量执行,保留节奏

滚动、resize、按钮连点用节流更合适:固定时间窗口内最多执行一次,第一次立即触发,尾部再补一次。

import { useEffect, useRef, useState } from "react";

export function useThrottle<T>(value: T, interval = 500): T {
  const [throttled, setThrottled] = useState(value);
  const last = useRef(0);
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    const now = Date.now();
    const remaining = interval - (now - last.current);
    if (remaining <= 0) {
      if (timer.current) clearTimeout(timer.current);
      last.current = now;
      setThrottled(value);
    } else if (!timer.current) {
      timer.current = setTimeout(() => {
        last.current = Date.now();
        timer.current = null;
        setThrottled(value);
      }, remaining);
    }
  }, [value, interval]);

  return throttled;
}

useLocalStorage:状态与持久化一步到位

把 useState 与 localStorage 同步,刷新后仍能恢复。注意 JSON 序列化与解析异常。

import { useState } from "react";

export function useLocalStorage<T>(key: string, initial: T) {
  const [value, setValue] = useState<T>(() => {
    try {
      const raw = localStorage.getItem(key);
      return raw ? (JSON.parse(raw) as T) : initial;
    } catch {
      return initial;
    }
  });

  const update = (next: T) => {
    setValue(next);
    try {
      localStorage.setItem(key, JSON.stringify(next));
    } catch {
      // 忽略写入失败(如隐私模式)
    }
  };

  return [value, update] as const;
}

工程化要点

  • Hook 内部的状态与副作用要自包含,避免依赖外部可变变量。
  • 清理函数别漏写,否则会造成定时器与事件监听泄漏。
  • 带泛型,让 Hook 在 TS 项目里能推导出准确类型,减少 any。
  • 把这类基础 Hook 收进一个 hooks 包,全项目统一引用,别各写一份。
T

test1

文章作者

为什么要把逻辑抽成自定义 Hook 组件里塞满防抖、节流、本地存储读写,会让业务组件越来越胖,且这些逻辑在多个页面反复...

分类
技术
发布时间
2026-09-17
字数
约 2293 字
阅读
9 次