React 中的“隐形英雄”:useRef 详解与实战指南

43 阅读5分钟

React 中的“隐形英雄”:useRef 详解与实战指南

在 React 的世界里,useState 是明星,useEffect 是导演,而 useRef 则像一位默默奉献的幕后英雄——它不引人注目,却在关键时刻不可或缺。很多初学者甚至不知道它的存在,直到遇到“定时器关不掉”、“输入框无法自动聚焦”等问题时,才意识到:原来 React 还藏着这样一把利器!

本文将带你彻底搞懂 useRef
✅ 它是什么?
✅ 和 useState 有什么区别?
✅ 为什么它能解决“组件重渲染导致数据丢失”的问题?
✅ 两大核心应用场景(DOM 引用 + 持久化存储)
✅ 配合 useEffect 实现类似 Vue 的 onMounted 行为

无论你是 React 新手,还是用了一段时间但对 useRef 仍感模糊,这篇文章都能让你豁然开朗!


一、useRef 是什么?一个“不会触发更新的小盒子”

useRef 是 React 提供的一个 Hook,用于创建一个可变的、持久化的引用对象。它的基本用法如下:

const myRef = useRef(initialValue);
  • 返回一个对象:{ current: initialValue }
  • myRef.current 可以被读取或修改;
  • 关键特性:修改 .current 不会触发组件重新渲染!

🎯 简单说:useRef 就像一个藏在组件内部的保险箱,你可以往里面放任何东西(数字、对象、DOM 元素……),而且无论组件刷新多少次,里面的东西都不会丢。


二、useRef vs useState:相同点与本质区别

✅ 相同点:

  • 都是 React 提供的“状态容器”;
  • 都能在函数组件中保存数据;
  • 都在组件多次渲染中保持引用不变(即 myRef === myRef 始终成立)。

❌ 本质区别:

特性useStateuseRef
是否触发重渲染✅ 是❌ 否
用途管理UI 状态(如计数、表单值)管理非 UI 数据(如定时器 ID、DOM 引用)
更新方式通过 setState()直接赋值 ref.current = ...
响应式✅ 响应式❌ 非响应式

💡 举个生活化例子:

  • useState 像是一个带通知功能的记事本:你一改内容,全家人都知道(页面刷新);
  • useRef 像是一个私密笔记本:你偷偷记东西,没人知道,但你需要时总能找到。

三、场景一:获取 DOM 元素 —— 让 React “操作真实节点”

在 React 中,我们通常通过状态驱动 UI,而不是直接操作 DOM。但在某些场景下,我们确实需要访问真实的 DOM 元素,比如:

  • 页面加载后自动聚焦输入框;
  • 播放视频、滚动到指定位置;
  • 集成第三方库(如地图、图表)。

这时,useRef 就派上用场了!

🔧 示例:自动聚焦输入框

import { useRef, useEffect } from 'react';

export default function App() {
  const inputRef = useRef(null); // 1. 创建 ref

  useEffect(() => {
    // 2. 组件挂载后,聚焦输入框
    inputRef.current.focus();
  }, []); // 空依赖 = 只在首次渲染执行(类似 Vue 的 onMounted)

  return <input ref={inputRef} />; // 3. 绑定到 DOM
}
关键点解析:
  • ref={inputRef}:告诉 React 把这个 <input> 的 DOM 对象存到 inputRef.current
  • useEffect(..., []):模拟 Vue 的 onMounted 生命周期;
  • inputRef.current.focus():调用原生 DOM 方法,实现聚焦。

✅ 这就是 React 推荐的“安全操作 DOM”的方式!


四、场景二:持久化存储非 UI 数据 —— 解决定时器“关不掉”的经典 bug

这是 useRef 最容易被忽视、却极其重要的用途。

❌ 错误写法(用普通变量):

let intervalId = null;
function start() {
  intervalId = setInterval(() => console.log('tick'), 1000);
}
function stop() {
  clearInterval(intervalId); // 可能为 null!
}

问题:每次组件重渲染(如 setState),intervalId 都会被重置为 null,导致 clearInterval 失效!

✅ 正确写法(用 useRef):

const intervalIdRef = useRef(null);

function start() {
  intervalIdRef.current = setInterval(() => console.log('tick'), 1000);
}

function stop() {
  clearInterval(intervalIdRef.current); // 始终能拿到正确的 ID!
}
为什么有效?
  • useRef 创建的对象在组件整个生命周期中保持不变
  • 即使 count 变化导致组件重新渲染,intervalIdRef.current 依然是上次设置的 ID;
  • 因此,stop() 能正确清除定时器,避免内存泄漏。

🚨 这是 React 开发中的高频陷阱,掌握 useRef 是避坑的关键!


五、useRef 的其他实用场景

1. 记录上一次的状态

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

// 使用
const prevCount = usePrevious(count);

2. 避免不必要的重渲染

当某个值变化不需要更新 UI 时,用 ref 存储比 state 更高效。

3. 存储可变的回调函数(配合 useCallback)

在复杂逻辑中,避免闭包捕获过期值。


六、常见误区与最佳实践

❌ 误区 1:用 let 声明 useRef

let myRef = useRef(); // 不推荐

✅ 应使用 const

const myRef = useRef(); // ✅

因为 useRef() 返回的是固定对象,不需要重新赋值。

❌ 误区 2:试图用 ref 触发渲染

myRef.current = newValue;
// 页面不会更新!

✅ 如果需要 UI 响应,请用 useState

✅ 最佳实践:

  • DOM 操作 → 用 ref + useEffect
  • 非 UI 数据(定时器、订阅 ID)→ 用 useRef
  • UI 状态(表单、开关、列表)→ 用 useState

七、总结:useRef 的三大核心价值

  1. 安全操作 DOM
    在 React 的声明式世界中,提供一条通往命令式 DOM 操作的“合法通道”。
  2. 持久化存储
    在组件多次渲染中保留任意值,解决“变量重置”问题。
  3. 性能优化
    避免因非必要状态更新导致的重渲染。

🌟 记住这句话:
“凡是不需要更新页面的数据,都该考虑用 useRef 来存。”


结语

useRef 虽然低调,却是 React 开发中不可或缺的工具。它不像 useState 那样耀眼,也不像 useEffect 那样复杂,但它在处理副作用、操作 DOM、优化性能等方面,扮演着无可替代的角色。

下次当你遇到:

  • “定时器停不掉”
  • “输入框无法自动聚焦”
  • “想记住上次的值”

请立刻想到:useRef,这位 React 的隐形英雄,正在等你召唤!

掌握它,你的 React 代码将更健壮、更高效、更专业!🚀