实现 useTimeout Hook
在 React 函数组件中正确使用 setTimeout 的自定义 Hook
问题
在 React 函数组件中直接使用 setTimeout 会遇到两个问题:
1. 重复创建定时器
每次组件重新渲染都会创建新的定时器,导致定时器不断累积。
function App() {
const [state, setState] = useState(1);
setTimeout(() => {
setState(state + 1);
}, 3000);
return <div>{state}</div>;
// 问题:state 更新后组件重新渲染,又创建新的定时器,变成每 3 秒 +1
}
2. 闭包陷阱
定时器回调函数捕获的是旧的状态值,无法获取最新状态。
function App() {
const [count, setCount] = useState(0);
const [countTimeout, setCountTimeout] = useState(0);
useEffect(() => {
setTimeout(() => {
setCountTimeout(count); // count 始终是 0
}, 3000);
setCount(5);
}, []);
return <div>{countTimeout}</div>;
// 问题:3 秒后 countTimeout 是 0,而不是 5
}
解答
使用 useRef 保存最新的回调函数,配合 useEffect 管理定时器生命周期:
function useTimeout(callback, delay) {
const memorizeCallback = useRef();
// 每次 callback 更新时,保存最新的回调函数
useEffect(() => {
memorizeCallback.current = callback;
}, [callback]);
// 创建定时器,组件卸载时清除
useEffect(() => {
if (delay !== null) {
const timer = setTimeout(() => {
memorizeCallback.current();
}, delay);
return () => {
clearTimeout(timer);
};
}
}, [delay]);
}
使用示例
function App() {
const [count, setCount] = useState(0);
useTimeout(() => {
console.log('当前 count:', count);
setCount(count + 1);
}, 3000);
return <div>{count}</div>;
}
关键点
- 使用
useRef存储回调函数,避免闭包陷阱,始终执行最新的回调 - 通过
useEffect的清理函数clearTimeout防止内存泄漏 - 将
delay作为依赖项,支持动态修改延迟时间 - 传入
null作为delay可以暂停定时器
目录