React 实战:从 setInterval 到 useInterval,一次搞懂定时器 Hook(还能暂停!)

👋 Hello 前端人!

今天我们不聊八股文,不聊性能优化,

我们聊聊一个"每个 React 人都会遇到,但又容易被劝退"的问题------定时器在 React 中的正确打开方式!


🧠 一、先来回忆下那个老朋友:setInterval

在原生 JS 里,我们经常这样写:

js 复制代码
setInterval(() => {
  console.log('每秒执行一次~');
}, 1000);

这没啥问题,

但是如果你把它搬进 React 组件里------

🤯 就可能翻车!

比如:

js 复制代码
function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setInterval(() => {
      console.log('count:', count);
      setCount(count + 1);
    }, 1000);
  }, []);
}

你以为每秒加一?

结果:它只会一直输出 count=0 😱

为什么?因为闭包!

回调函数捕获的是"当时的 count 值",并不会更新。

所以 setInterval 里的函数其实拿的是"老的状态"!

这时候,React 新手就开始懵逼三连:

  • "为啥不更新?"
  • "我是不是忘了加依赖?"
  • "这 useEffect 是不是坏了?"

其实都没坏,是我们和闭包斗了个寂寞


🧩 二、来点优雅的:自定义 Hook ------ useInterval

要彻底解决这个问题,我们可以封装一个属于自己的 Hook。

目标:

  • 能正常执行 callback,不被闭包坑
  • 能自由控制延迟(甚至暂停)

看代码

js 复制代码
import { useEffect, useRef } from 'react';

function useInterval(callback, delay) {
  // 用 useRef 保存 callback 引用
  const savedCallback = useRef();

  // 每次 callback 变化时更新 ref
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // 启动定时器 + 清除逻辑
  useEffect(() => {
    if (delay === null) return; // 支持暂停
    const tick = () => savedCallback.current();
    const id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

export default useInterval;

⚙️ 三、逐行拆解(useRef 是关键!)

useRef 的真正作用是什么?

很多人以为 useRef 只是用来"获取 DOM 引用"。

其实不止如此!

useRef 是一个可变的盒子(Mutable Box)

你往里塞什么,它都不会因为重新渲染而丢掉。

简单理解:

ini 复制代码
const box = useRef();
box.current = '我在 React 的所有重渲染中永存!';

所以在 useInterval 中:

ini 复制代码
savedCallback.current = callback;

这一步就像把最新的 callback 放进一个"保险柜"里。

即使组件重新渲染,定时器里的函数永远能拿到最新的 callback。


四、那怎么实现"暂停"?

我们注意这行:

ini 复制代码
if (delay === null) return;

这其实是一个非常巧妙的小技巧!✨

当你传入 delay = null 时,
useEffect 会直接跳过创建定时器的逻辑。

这样,定时器相当于被"暂停"了。

你可以这样用👇

js 复制代码
function App() {
  const [count, setCount] = useState(0);
  const [delay, setDelay] = useState(1000);

  useInterval(() => setCount(c => c + 1), delay);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setDelay(1000)}>开始</button>
      <button onClick={() => setDelay(null)}>暂停</button>
    </div>
  );
}

点击"暂停"按钮时,delay 变为 null
useEffect 检测到变化 → 不再创建 interval → 定时器被清除!💥


🧙‍♂️ 五、为什么不直接写在组件里?

因为如果你直接在组件中使用 setInterval

  • 每次组件重新渲染都会重新创建 interval
  • callback 会被闭包锁死
  • 清除不及时还可能内存泄漏

而封装成 useInterval 后:

  • 逻辑独立、易复用
  • 自动清理旧定时器
  • 支持最新状态 + 可暂停
  • 不用每次重新写一大坨 useEffect

可谓"一 Hook 在手,天下我有"。


🎨 六、扩展:实现"开始/暂停/重置"全家桶

来个加强版:

jsx 复制代码
import { useRef, useEffect, useState } from 'react';

function useInterval(callback, delay) {
  const savedCallback = useRef();
  const [active, setActive] = useState(true);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (!active || delay === null) return;
    const tick = () => savedCallback.current();
    const id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay, active]);

  return {
    pause: () => setActive(false),
    resume: () => setActive(true),
    toggle: () => setActive(a => !a),
  };
}

现在你可以在组件里这样玩:

jsx 复制代码
function Timer() {
  const [count, setCount] = useState(0);
  const { pause, resume, toggle } = useInterval(() => setCount(c => c + 1), 1000);

  return (
    <div>
      <h3>计数:{count}</h3>
      <button onClick={pause}>暂停</button>
      <button onClick={resume}>继续</button>
      <button onClick={toggle}>切换</button>
    </div>
  );
}

轻轻一点,秒变控制台!

从 setInterval 到自定义 Hook,你的代码从"混乱"变"优雅"🌈。


🧭 七、总结:这篇文章你得到了什么?

概念 含义 你应该记住的点
setInterval 原生定时器 React 内使用容易被闭包坑
useRef 可变引用盒子 保存最新 callback,不随渲染重置
useEffect 副作用钩子 启动 + 清除定时器
delay=null 暂停机制 通过跳过 useEffect 实现暂停
自定义 Hook 封装逻辑 让组件更干净、更复用

🐣 尾声:从"小钩子"看大世界

React 的世界里,一切都是 Hook。
useStateuseEffect 是基础,

但当你开始学会封装自己的 useXxx

你就真的在迈向进阶 React 开发者的道路上了!


如果你想进一步拓展,我推荐你可以试试:

  • useInterval 做一个"打卡倒计时"组件;
  • 或者封装 useTimeout
  • 甚至做一个"多定时任务管理器"。

💬 最后,求个三连支持(点赞 + 收藏 + 评论)!

因为掘金的算法告诉我------

你点得越多,我就越像个懂前端的博主 😎

相关推荐
闲不住的李先森3 小时前
乐观更新
前端·react.js·设计模式
笔尖的记忆3 小时前
【前端架构和框架】react组件化&数据流
前端·面试
zhangzelin8884 小时前
TypeScript入门指南:JavaScript的类型化超集
前端·javascript·其他·typescript
lichenyang4534 小时前
流式聊天界面实现解析:从零到一构建实时对话体验
前端
天蓝色的鱼鱼4 小时前
Turbopack vs Webpack vs Vite:前端构建工具三分天下,谁将胜出?
前端·webpack
软件技术NINI4 小时前
html css js网页制作成品——化妆品html+css+js (7页)附源码
javascript·css·html
用户841794814564 小时前
vxe-table 实现列头授权自定义插槽模板,自定义输入框
前端
im_AMBER4 小时前
Web 开发 24
前端·笔记·git·学习
小小前端_我自坚强4 小时前
前端算法相关详解
前端·算法