深入理解 React 中的 useRef:不只是获取 DOM 元素

在 React 函数组件的世界中,useRef 是一个常被提及但又容易被误解的 Hook。很多初学者第一次接触它时,往往只把它当作"获取 DOM 元素"的工具;而随着使用深入,又可能会疑惑:为什么不能用 useRef 替代 useState 来避免不必要的重渲染?本文将结合两个典型示例,系统梳理 useRef 的核心机制、使用场景与常见误区,帮助你真正掌握这个"默默奉献"的 Hook。


一、useRef 的基本能力:持久化引用对象

useRef 的本质是创建一个可变且持久化的引用对象 。它的返回值是一个普通 JavaScript 对象,结构为 { current: initialValue }。关键在于:

  • 每次组件重新渲染时,useRef 返回的是同一个对象引用
  • 修改 .current 属性不会触发组件重新渲染
  • 它不参与 React 的响应式更新机制,因此被称为"非响应式存储"。

这与 useState 形成鲜明对比:useState 的状态变更会触发 UI 更新,而 useRef 的变更则"静默"发生。


二、场景一:获取 DOM 元素并操作

最常见的 useRef 用法是绑定到 JSX 元素上,从而在组件逻辑中直接操作 DOM。

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

export default function App() {
  const [count, setCount] = useState(0);
  const inputRef = useRef(null);

  useEffect(() => {
    // 组件挂载后,inputRef.current 指向真实的 <input> 元素
    inputRef.current.focus();
  }, []);

  return (
    <>
      <input ref={inputRef} />
      {count}
      <button onClick={() => setCount(count + 1)}>count ++</button>
    </>
  );
}

在这个例子中:

  • 初始渲染时,inputRef.currentnull,因为 DOM 尚未生成;
  • React 在完成 DOM 挂载后,会自动将对应的 DOM 节点赋值给 ref.current
  • useEffect(依赖项为空数组)在首次挂载后执行,此时 inputRef.current 已是有效的 <input> 元素,调用 .focus() 实现自动聚焦。

值得注意的是:即使 inputRef.currentnull 变为 DOM 节点,组件也不会重新渲染 。这正是 useRef 的设计初衷------提供一种不干扰 React 渲染流程的方式来访问或存储数据。


三、场景二:存储可变值以避免状态重置

除了操作 DOM,useRef 还非常适合用于在多次渲染之间持久化存储可变值,尤其是在处理副作用(如定时器)时。

考虑以下错误写法:

javascript 复制代码
// ❌ 错误:使用普通变量存储定时器 ID
let intervalId = null;

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

function stop() {
  clearInterval(intervalId); // 可能为 null!
}

问题在于:每当 count 状态更新,整个函数组件会重新执行,let intervalId = null 会被再次初始化,导致之前保存的定时器 ID 丢失。结果是:

  • 多次点击"开始"会创建多个定时器;
  • "停止"按钮无法清除旧的定时器;
  • 造成内存泄漏逻辑混乱

正确的做法是使用 useRef

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

export default function App() {
  const intervalId = useRef(null);
  const [count, setCount] = useState(0);

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

  function stop() {
    clearInterval(intervalId.current);
  }

  return (
    <>
      <button onClick={start}>开始</button>
      <button onClick={stop}>停止</button>
      {count}
      <button onClick={() => setCount(count + 1)}>count ++</button>
    </>
  );
}

这里的关键在于:

  • useRef(null) 在组件整个生命周期内始终返回同一个对象
  • 即使 count 更新导致组件重新渲染,intervalId.current 依然保留着上次设置的定时器 ID;
  • 因此 clearInterval 能正确清除目标定时器,避免资源泄露。

四、useRef 与 useState 的核心区别

虽然 useRefuseState 都能"存值",但它们的设计目标截然不同:

特性 useState useRef
是否触发重渲染 ✅ 是 ❌ 否
值变更是否被 React 跟踪 ✅ 是(通过调度更新) ❌ 否
适用场景 驱动 UI 变化的状态 存储不需触发 UI 更新的可变数据
初始值 每次调用 useState(initial) 仅在首次生效 useRef(initial) 的初始值也仅在首次生效,但后续 .current 可任意修改

常见误区:能否用 useRef 替代 useState?

不能。

假设你试图用 useRef 存储计数器值以"避免重渲染":

javascript 复制代码
const countRef = useRef(0);
// ...
<button onClick={() => countRef.current++}>+1</button>
<p>{countRef.current}</p>

你会发现:点击按钮后,页面上的数字不会更新 !因为 React 并不知道 countRef.current 发生了变化,自然不会重新执行渲染逻辑。

结论

  • 如果你需要同时存储值并更新 UI ,必须使用 useState
  • 如果你只需要在不触发重渲染的前提下保存中间状态或引用 (如定时器 ID、前一次的 props、滚动位置等),才应使用 useRef

五、useRef 的典型应用场景总结

  1. 访问 DOM 节点

    如聚焦输入框、测量元素尺寸、触发动画等。

  2. 持久化存储可变值

    • 定时器/延时器 ID(setInterval / setTimeout
    • WebSocket 实例
    • 第三方库的实例(如地图、图表对象)
    • 上一次的 props 或 state(用于对比)
  3. 跨渲染保持状态而不触发更新

    例如记录组件是否已挂载(在异步回调中判断是否还能安全 setState)。


结语

useRef 虽然名字里有 "ref",但它远不止是"获取 DOM 的工具"。它是一个轻量级、非响应式的持久化容器,在需要"记住某些东西但又不想打扰 React 渲染流程"时大显身手。

正确使用 useRef,能让你的组件更高效、更健壮;而误用它(如试图替代状态管理),则会导致 UI 不更新或逻辑错乱。理解其"非响应式"和"引用持久化"的两大特性,是掌握这一 Hook 的关键。

在实际开发中,当你遇到以下情况时,不妨想想 useRef

  • "我需要在组件里保存一个值,但它变了不需要刷新页面。"
  • "我想在挂载后操作某个 DOM 元素。"
  • "我的定时器怎么关不掉了?是不是 ID 丢了?"

答案,往往就在 useRef 之中。

相关推荐
MoXinXueWEB2 小时前
前端页面获取不到url上参数值
前端
低保和光头哪个先来2 小时前
场景6:对浏览器内核的理解
开发语言·前端·javascript·vue.js·前端框架
想要一只奶牛猫2 小时前
Spring Web MVC(三)
前端·spring·mvc
奋飛2 小时前
微前端系列:核心概念、价值与应用场景
前端·微前端·micro·mfe·什么是微前端
进击的野人4 小时前
Vue Router 深度解析:从基础概念到高级应用实践
前端·vue.js·前端框架
北慕阳4 小时前
健康管理前端记录
前端
1024小神4 小时前
cloudflare的worker中的Environment环境变量和不同环境配置
前端
栀秋6664 小时前
从零开始调用大模型:使用 OpenAI SDK 实现歌词生成,手把手实战指南
前端·llm·openai
l1t4 小时前
DeepSeek总结的算法 X 与舞蹈链文章
前端·javascript·算法