资讯动态

深聊 useRef:比你知道的再多一点

发布时间:2026/8/8 6:46:41 来源:尧图企业网站定制
用 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 赋予你的“超能力”去窥探和影响那个响应式世界之外的物理空间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价