当前位置: 首页 > news >正文

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

http://www.jsqmd.com/news/196433/

相关文章:

  • 3个为什么你的Windows系统需要Dism++终极优化方案
  • 岛屿规划的3个关键突破:从新手到专家的进阶指南
  • 一文说清可执行文件在桌面应用中的加载机制
  • notepad-- macOS文本编辑器完整配置指南:新手轻松上手指南
  • D2DX游戏优化:让暗黑破坏神2在现代PC上重获新生
  • 减小音频文件体积可有效缩短Fun-ASR识别等待时间
  • 3步解锁!明日方舟基建自动化管理的秘密武器
  • 胡桃工具箱:为原神玩家量身打造的桌面神器
  • git format-patch生成补丁文件附语音说明
  • IDA Pro中ARM指令译码技巧:通俗解释条件执行与移位操作
  • 手把手教你用AI分离音乐人声和伴奏
  • Dism++终极指南:7步解决Windows系统卡顿问题
  • 基于springboot框架的船舶物流运输管理系统设计vue
  • B站视频智能解锁:一键转换m4s缓存的高效解决方案
  • 基于springboot框架的高校实验室耗材管理系统vue
  • 网易云音乐批量下载技术实战指南:构建个人音乐资源库
  • React 性能优化避坑指南:彻底搞懂 useMemo、useCallback 与闭包陷阱
  • FieldTrip脑电信号分析工具箱完全使用指南:从入门到精通
  • 5分钟轻松搭建:原神私服零基础完全指南
  • 如何通过VAD检测提升Fun-ASR语音识别效率?附GPU资源节省方案
  • 3个步骤让OpenProject成为你的项目管理画布:从混乱到高效协作
  • CSDN博客之星评选考虑Fun-ASR主题文章
  • ImageStrike:CTF图像隐写分析的终极解决方案
  • 如何导出Fun-ASR批量处理结果为CSV或JSON?自动化流程建议
  • 基于springboot框架的高校教材征订进销存管理系统vue springboot
  • 免费音乐解锁工具:浏览器端轻松转换加密音频文件(2025实用指南)
  • 5分钟零基础搭建原神私服:图形化操作完全指南
  • Calibre-Web豆瓣插件完整配置手册:轻松解决元数据获取难题
  • MathType公式编号样式语音调整功能展望
  • B站缓存视频格式转换全攻略:m4s文件完美转MP4