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 次
