在 React 函数组件中,我们常用 useState 来管理会触发页面更新的状态。但有些数据------比如定时器 ID、DOM 节点引用、或上一次的计算结果------我们只想记住它,却不想因为它的变化而重新渲染组件 。这时候,useRef 就派上了大用场。
本文将通过两个典型场景(自动聚焦输入框 和 启动/停止计时器 ),深入讲解 useRef 的核心价值:可变、持久、非响应式。
一、useRef 是什么?
useRef 是 React 提供的一个 Hook,用于创建一个可变的、跨渲染周期持久存在的引用对象。
ini
const ref = useRef(initialValue);
- 返回值是一个对象:
{ current: initialValue } ref.current可读可写- 修改
ref.current不会触发组件重新渲染 - 在组件整个生命周期内,
ref对象本身(引用)保持不变
✅ 简单说:
useRef就是一个"私有小盒子",你可以往里存任何东西,React 完全不管,也不会因此重画页面。
二、对比 useState:相同与不同
| 特性 | useState |
useRef |
|---|---|---|
| 都能存储数据 | ✅ 是 | ✅ 是 |
| 数据可变 | ✅ 通过 setState |
✅ 直接赋值 ref.current = ... |
| 是否触发 re-render | ✅ 是(响应式) | ❌ 否(非响应式) |
| 适合存什么 | UI 状态(如表单值、开关) | DOM 引用、定时器 ID、上一次的值等 |
💡 关键区别:
useState是"状态驱动 UI"useRef是"记住东西,但不影响 UI"
三、场景 1:获取 DOM 元素 ------ 自动聚焦
ini
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // 让输入框自动获得焦点
}, []);
return <input ref={inputRef} placeholder="自动聚焦" />;
useRef(null)创建一个初始为空的引用- 通过
ref={inputRef},React 将真实的<input>DOM 元素赋值给inputRef.current - 在
useEffect(相当于 Vue 的onMounted)中调用.focus(),实现挂载后自动聚焦 - 整个过程不涉及状态更新,无额外渲染
✅ 这是 useRef 最经典的用途之一:操作 DOM。
四、场景 2:保存定时器 ID ------ 启动与清理
scss
const intervalId = useRef(null);
function start() {
intervalId.current = setInterval(() => {
console.log('tick~~~~');
}, 1000);
}
function stop() {
clearInterval(intervalId.current);
intervalId.current = null;
}
// 组件卸载时自动清理
useEffect(() => {
return () => {
if (intervalId.current) {
clearInterval(intervalId.current);
}
};
}, []);
🔑 为什么必须用 useRef?
setInterval返回一个唯一的数字 ID(如123)- 我们需要在
stop()或组件卸载时,用这个 ID 调用clearInterval来停止定时器 - 如果用普通变量(如
let id = null),每次组件 re-render(比如点击count++)都会重置为null,导致无法清除定时器 → 内存泄漏! - 而
useRef的current值在多次渲染中保持不变,完美保存了定时器 ID
✅ 核心优势:
- 记住上一次的状态(定时器 ID)
- 不因
count变化而丢失 - 修改它不会触发不必要的渲染
- 避免陷入"渲染 → 启动新定时器 → 再渲染"的死循环
五、useRef 的本质:可持久化的引用对象
从你提供的代码可以看出:
scss
useEffect(() => {
console.log(intervalId.current);
}, [count]); // 即使 count 变化,intervalId.current 依然保留之前的值
这证明了:
useRef创建的对象在组件整个生命周期中是同一个引用- 它的
.current属性可以被任意修改 - React 完全忽略对它的修改,既不响应,也不追踪
🎯 这正是
useRef的设计哲学: "我只负责记住,不负责通知。"
六、总结:useRef 的三大核心用途
- 引用 DOM 元素
→ 如自动聚焦、滚动到指定位置、获取尺寸等 - 存储可变但无需触发渲染的数据
→ 如定时器 ID、WebSocket 实例、请求取消 token 等 - 保存上一次的 props 或 state(进阶)
→ 虽然你未提及,但原理相同:用useRef记住旧值,在useEffect中更新
✅ 最后提醒
- 不要用
useRef存放 UI 状态 (如表单值),那应该用useState - 不要依赖
useRef触发更新,它天生是非响应式的 - 善用
useRef+useEffect清理函数,避免内存泄漏
💬 正如你的注释所说:
useRef------ 默默奉献的存储能力
useState------ 响应式的状态驱动
掌握这两者的分工,你就掌握了 React 函数组件状态管理的精髓!