深聊 useRef:比你知道的再多一点
用 ref 偷看 DOM 的“底牌”用 ref 存储“不渲染”的秘密大家好在日常开发中useState和useEffect几乎是我们的“肌肉记忆”但useRef这个 Hook 却常常被简化成一个“DOM 获取器”。今天带你重新认识这位“熟悉的陌生人”。你会发现useRef不仅仅是document.getElementById的 React 替代品它更是 React 渲染机制里一个巧妙的“后门”。1. 从“为什么需要 Ref”说起在 React 的世界里我们提倡“数据驱动视图”意思是你只管改数据UI 会自己变。这让我们告别了过去“命令式”的 DOM 操作比如document.getElementById(input).focus()极大地提升了开发效率和性能。但“完全禁止操作 DOM”是不现实的。React 官方也清楚这一点于是提供了useRef这个 Hook让我们在必要时可以“抓住”真实的 DOM 节点。1.1 回顾一下 DOM 操作的代价在 React 和 Vue 出现之前我们直接用 JS 操作 DOM。JS 引擎V8和渲染引擎是分离的每次 JS 访问 DOM 都是一次“跨界”。这种跨引擎的通信是有性能开销的。所以 React 的“虚拟 DOM”和“批量更新”本质上就是为了减少这种跨界的频率。说白了就是React 推荐你用state控制 UI而不是自己手动改 DOM。但如果非要去改请走useRef这个“官方通道”。2.useRef的两大核心场景我把useRef的用法抽象为两类绑定 DOM 节点和存储可变数据。虽然它们底层原理一样但使用场景截然不同。2.1 场景一绑定 DOM 节点“连接”真实世界这是useRef最基础的应用。比如我们想让输入框在页面加载后自动获得焦点。代码示例自动聚焦import React, { useRef, useEffect } from react; const LoginInput () { // 1. 声明一个 ref 对象初始值为 null const inputRef useRef(null); // 2. 在组件挂载完成后手动聚焦 useEffect(() { // 此时 inputRef.current 就是真实的 input DOM 元素 if (inputRef.current) { inputRef.current.focus(); } }, []); // 空依赖数组确保只执行一次 return ( // 3. 通过 ref 属性将 DOM 节点挂载到 inputRef.current 上 input ref{inputRef} typetext placeholder用户名 / ); };深度解析useRef(null)创建了一个对象{ current: null }。当 React 将input渲染到真实 DOM 后会在“提交阶段”commit phase更新inputRef.current的指向。useEffect在 DOM 绘制完成后执行所以我们能在其中安全地操作inputRef.current。2.2 场景二存储可变数据“跨渲染”的容器这是useRef最容易被忽略但极其强大的功能。它像是一个组件实例上的“全局变量”可以用来存储任何可变值并且改变它不会触发组件重新渲染。代码示例记录点击次数import React, { useRef, useState } from react; const ClickCounter () { // 定义一个类似“全局变量”的计数器 const countRef useRef(0); // 这个 state 只用来“强制”触发渲染它的值不重要 const [, setDummy] useState(0); const handleClick () { // 修改 ref.current 不会触发渲染 countRef.current 1; console.log(点击次数, countRef.current); // 手动触发一下渲染以便看到 UI 上的变化虽然我们用 ref 存值 setDummy(prev prev 1); }; return ( div {/* 你会发现即使 setDummy 触发了渲染countRef.current 的值依然保持不变 */} p点击次数用 ref 存储{countRef.current}/p button onClick{handleClick}增加/button /div ); };useState是给 React看的变了它就得干活渲染useRef是给你自己用的怎么变都行别打扰 React。2.3useRefvsuseState核心差异为了让你更清晰我整理了一个对比表格特性useStateuseRef更新方式setXxx(newValue)ref.current newValue触发渲染会更新后重绘不会静默修改存储内容用于渲染的数据UI 状态不用于渲染的数据副作用、实例变量读取时机渲染期间读取任何时候事件、Effect、定时器典型用途表单输入、控制显示隐藏DOM 引用、定时器 ID、跨渲染变量3. 进阶useRef的“不变”原理底层触发机制深度解析你可能会好奇为什么useRef修改值不会触发渲染而setState一改就立刻重绘而且为什么useRef能在多次渲染之间保持同一个引用答案就在它的“出生地”和 React 的任务调度机制里。useRef实际上是在 React 的“内部 Fiber 节点”上挂载了一个属性。每次组件函数执行渲染时useRef返回的都是同一个内存地址的对象。React 只会在首次渲染时初始化current属性后续渲染直接复用。但更深层次的区别在于 React 的“更新队列”和“调度中心”是如何看待这两个 Hook 的useState的“强制唤醒”机制当你调用setCount时React 会做两件非常重的事情。首先它会在该 Fiber 节点的更新队列updateQueue中创建一个更新对象Update Object其次它会向 React 的调度器Scheduler发起一个“优先级任务”请求。这个任务会强制组件重新执行Re-render哪怕你的新值和旧值一模一样除非使用了updater函数做惰性判断。重新执行后React 会走完完整的“调度 - 协调Diffing- 提交Commit”流水线。useRef的“静默修改”机制当你修改ref.current时你仅仅是在直接修改 Fiber 节点memoizedState中的一个普通 JavaScript 对象属性。关键在于这个突变Mutation完全绕过了 React 的状态更新机制。因为没有新的 Update 对象被创建调度器的任务队列空空如也React 根本感知不到这个变化。在 React 的理念里“没有 Update 就意味着没有状态变化既然状态没变那就没有重新渲染的必要”。换个更形象的比喻useState的setter像是你在公司群里了全体成员React并大喊“出大事了快开会重新渲染”而修改useRef.current就像是你默默在自己的工位笔记本上涂改了一笔老板React根本不知道自然也不会组织全员大会。这种机制带来的最大好处就是极致的性能优化。如果你的组件需要频繁记录一些“与 UI 无关”的数据比如滚动事件的节流标记、动画的帧率计数器、定时器的 ID使用useRef可以确保这些高频操作完全不影响 React 的主渲染线程从而避免掉帧和无意义的计算。4. 常见踩坑与最佳实践4.1 不要在渲染过程中读写ref.current// ❌ 错误示例 const App () { const ref useRef(0); // 在渲染函数中修改 ref会导致不可预测的结果甚至破坏并发渲染 ref.current ref.current 1; return div{ref.current}/div; };最佳实践只在事件处理函数、useEffect或setTimeout等“副作用”中操作ref.current。4.2 用ref来“缓存”函数或定时器有时候我们需要在组件卸载时清除定时器。用useRef来存储定时器 ID 是最优雅的方式。const Timer () { const timerIdRef useRef(null); const startTimer () { timerIdRef.current setInterval(() { console.log(tick); }, 1000); }; const stopTimer () { clearInterval(timerIdRef.current); }; // 在组件卸载时清理 useEffect(() { return () { if (timerIdRef.current) { clearInterval(timerIdRef.current); } }; }, []); // ... return JSX with buttons };4.3 别把所有东西都往ref里放useRef是用来存放“副作用”的不是用来存业务数据的。如果你发现某个值需要被 UI 展示那它必须是 State如果某个值的变化需要 UI 响应那它也必须是 State。一句话总结UI 要用的用useStateUI 不关心的用useRef。5. 总结useRef是 React 设计中非常精妙的一环。它既是我们与命令式 DOM 操作之间的桥梁也是我们绕过 React 响应式系统的一个“安全通道”。通过今天的文章希望你能记住三点用ref抓 DOM替代document.querySelector在useEffect中操作。用ref存变量存储那些“变了也不需要重新渲染”的数据。牢记不变性useRef在整个生命周期中保持引用不变这是其所有特性的基石。下次当你再次写下useRef时别忘了你正在使用 React 赋予你的“超能力”去窥探和影响那个响应式世界之外的物理空间。

相关新闻

2026年面试题(一)

2026年面试题(一)

一、前端都有哪些技术1)基础三层HTML:页面结构,标签搭建页面骨架CSS:页面样式,布局、颜色、动画;衍生:CSS3、SCSS/Sass、Less、Tailwind CSSJavaScript:页面交互逻辑;ES6…

2026/11/7 4:22:01 阅读更多 →
AI绘图工具实战指南:从Mermaid到Draw.io,重构技术图表工作流

AI绘图工具实战指南:从Mermaid到Draw.io,重构技术图表工作流

1. 从“手动画图”到“AI驱动”:一次工作流的彻底革新如果你和我一样,曾经为了画一张清晰的技术架构图、业务流程图或者系统部署图,在 Draw.io、Visio 甚至 PPT 里耗费数小时,反复调整框线、对齐箭头、斟酌配色,最后出…

2026/11/11 19:09:42 阅读更多 →
OpenClaw实战:从零部署AI Agent框架,实现自然语言驱动应用开发

OpenClaw实战:从零部署AI Agent框架,实现自然语言驱动应用开发

1. 从“手动挡”到“自动驾驶”:AI开发范式的革命性转变如果你是一名开发者,或者对AI应用开发感兴趣,那么最近一定被“AI Agent”、“智能体”这些词刷屏了。传统的AI应用开发是什么样子的?那感觉就像开一辆“手动挡”的老爷车。你…

2026/11/11 12:23:40 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/15 5:13:35 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/15 5:13:35 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/15 5:13:35 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/15 3:37:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/15 4:18:38 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/15 5:13:35 阅读更多 →

日新闻

周新闻

月新闻