news 2026/4/16 19:10:59

React 进阶:useRef —— 那个“只做事不说话”的幕后英雄

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React 进阶:useRef —— 那个“只做事不说话”的幕后英雄

在 React 的学习过程中,你一定遇到过这种困境:

  • 我想操作 DOM(比如让输入框自动聚焦),但 React 告诉我不要直接操作 DOM。
  • 我想保存一个变量,不希望它重置,但也不希望它的改变触发组件重新渲染。
  • 我在useEffect里怎么都拿不到最新的 State 值(闭包陷阱)。

这时候,你需要的就是useRef

一、 什么是 useRef?

简单来说,useRef创建了一个普通的 JavaScript 对象,它长这样:

{ current: ... // 这里存着你的值 }

它有两个核心特性,必须死记硬背:

  1. 引用透传:在组件的整个生命周期内,这个对象永远是同一个(引用地址不变)。
  2. 变更不渲染:修改ref.current的值,不会触发组件重新渲染(这与useState完全相反)。

打个比方:

  • useState像是橱窗里的模特。换了衣服(状态改变),大家都能看见(页面重绘)。
  • useRef像是你口袋里的记事本。你写了什么(修改值),只有你自己知道,外面的人看不见(页面会重绘)。

二、 场景一:访问 DOM 节点(最常见用法)

React 是声明式的,我们通常不需要直接碰触 DOM。但在某些场景下(管理焦点、文本选择、媒体播放、强制动画等),我们需要“逃生舱”。

import { useRef, useEffect } from 'react'; export default function TextInputWithFocusButton() { // 1. 创建一个 ref,初始值为 null const inputEl = useRef(null); const onButtonClick = () => { // 3. 通过 .current 访问真实的 DOM 节点 // 注意:React 会在组件挂载后,自动把 DOM 赋给 current inputEl.current.focus(); console.log('输入框现在的宽度是:', inputEl.current.offsetWidth); }; return ( <div> {/* 2. 把 ref 绑定到 JSX 元素上 */} <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>点我聚焦输入框</button> </div> ); }

注意:不要过度使用 Ref 操作 DOM。如果你发现自己在用 Ref 去修改 DOM 的内容(如inputEl.current.value = 'hello'),这通常意味着你写错了,应该用useState控制。

三、 场景二:存储“幕后”变量(解决闭包陷阱)

还记得上一篇文章里的“闭包陷阱”吗?定时器里永远只能读到旧的 count。

除了把 count 加入依赖项,useRef 提供了一种更巧妙的解法:“逃课”大法。

既然闭包锁住的是变量的引用,那我们就创建一个永远不变的容器(Ref),把最新的值随时放进去。

import { useState, useEffect, useRef } from 'react'; export default function Timer() { const [count, setCount] = useState(0); // 1. 创建一个 Ref 用来“偷运”最新的 count const latestCountRef = useRef(count); // 2. 每次渲染,都把最新的 count 写入 Ref // 这不会触发重绘,因为修改 Ref 是副作用 latestCountRef.current = count; useEffect(() => { const timer = setInterval(() => { // 3. 定时器里不读 State,而是读 Ref // 因为 Ref 对象的引用地址从未改变,所以闭包能一直访问到它 // 而 .current 属性总是被我们要么更新为最新的 console.log('定时器读取到的最新值:', latestCountRef.current); }, 1000); return () => clearInterval(timer); }, []); // ✅ 依赖项可以为空!因为 Ref 对象本身是稳定的 return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }

原理解析:

  • count是每次渲染都不同的数字(值类型)。闭包一旦形成,捕获的是当年的那个数字。
  • latestCountRef是一个对象(引用类型)。闭包捕获的是这个对象的地址。哪怕里面的.current变了,只要地址没变,闭包就能顺藤摸瓜找到最新的值。

四、 场景三:记录“上一次”的值

有时候我们需要知道状态“上一次”是什么,比如判断股票是涨了还是跌了。React 没有提供usePrevious这样的 Hook,我们可以用useRef自己造一个。

import { useState, useEffect, useRef } from 'react'; function Counter() { const [count, setCount] = useState(0); // 用于存储上一次的值 const prevCountRef = useRef(); useEffect(() => { // 渲染完成后,更新 ref // 只有在下一次渲染时,我们才能拿出来对比 prevCountRef.current = count; }); // 每次渲染后都执行 // 在本次渲染中,prevCountRef.current 还是旧值 // 因为 useEffect 是在渲染结构提交到屏幕**之后**才运行的 const prevCount = prevCountRef.current; return ( <div> <h1>当前: {count}</h1> <h2>上一次: {prevCount}</h2> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }

五、 灵魂拷问:为什么不用普通变量?

新手常问:“为什么不用let variable = 0定义在组件外面或者里面,非要用useRef?”

1. 为什么不能定义在组件里面

function App() { let timerId = 0; // ❌ 错误 // ... }

原因:组件每次重新渲染,函数就会重新执行。timerId会被重置为 0。你辛辛苦苦存的数据瞬间丢失。

2. 为什么不能定义在组件外面

let timerId = 0; // ❌ 错误(除非是单例) function App() { // ... }

原因: 如果你的页面上有 5 个 组件,它们会共享同一个全局的 timerId。一个组件改了,别的组件全乱套了。

useRef 保证了数据是“也就是组件实例独享的”,且“穿越渲染周期而不丢失”。

总结:useRef vs useState

特性useStateuseRef
主要用途存储直接影响视图的数据存储 DOM 引用、定时器 ID、无关视图的数据
数据变化时触发组件重新渲染不触发重新渲染
读取时机渲染过程中直接读取通常在 useEffect 或事件处理函数中读取
心智模型组件的状态(State)组件的实例变量(Instance Variable)

当你下一次想在 React 里存点东西,但又不想因为它变了而导致页面莫名其妙闪烁(重绘)时,请立刻想起useRef

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:20:41

DINOv2预训练模型实战避坑指南:输入尺寸与位置编码的正确配置

DINOv2预训练模型实战避坑指南&#xff1a;输入尺寸与位置编码的正确配置 【免费下载链接】dinov2 PyTorch code and models for the DINOv2 self-supervised learning method. 项目地址: https://gitcode.com/GitHub_Trending/di/dinov2 为什么你的DINOv2模型总是报维度…

作者头像 李华
网站建设 2026/4/16 12:33:08

IDA Pro中ARM指令译码技巧:通俗解释条件执行与移位操作

IDA Pro中ARM指令译码实战&#xff1a;看懂条件执行与移位背后的控制流真相你有没有在IDA Pro里看到过这样的代码&#xff1a;CMP R0, #0 ADDEQ R1, R1, #1 ADDNE R2, R2, #1表面看是三条顺序执行的指令&#xff0c;但逻辑上却像是一个if-else分支&#xff1f;或者见过…

作者头像 李华
网站建设 2026/4/16 15:51:28

中国大学MOOC终极下载神器:mooc-dl完整使用教程

还在为网速问题错过名校课程而烦恼&#xff1f;mooc-dl这款免费开源工具能帮你轻松下载中国大学MOOC平台的所有课件资源&#xff0c;实现真正的离线学习自由&#xff01;无论是视频课程、PDF讲义还是课后资料&#xff0c;都能一键批量保存到本地。 【免费下载链接】mooc-dl :ma…

作者头像 李华
网站建设 2026/4/16 9:47:53

基于springboot框架的船舶物流运输管理系统设计vue

目录船舶物流运输管理系统设计摘要开发技术核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;船舶物流运输管理系统…

作者头像 李华
网站建设 2026/4/16 12:27:56

T触发器的特性方程推导:系统学习同步时序电路

从翻转到计数&#xff1a;深入理解T触发器的内在逻辑你有没有想过&#xff0c;一个简单的“翻转”动作&#xff0c;如何撑起整个数字世界的节奏&#xff1f;在无数闪烁的LED、飞速运转的CPU和精准跳动的时钟背后&#xff0c;藏着一种极其简洁却威力无穷的电路单元——T触发器。…

作者头像 李华
网站建设 2026/4/15 20:35:12

音乐解密新纪元:Unlock Music全方位使用手册

音乐解密新纪元&#xff1a;Unlock Music全方位使用手册 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库&#xff1a; 1. https://github.com/unlock-music/unlock-music &#xff1b;2. https://git.unlock-music.dev/um/web 项目地址: https://gitcod…

作者头像 李华