useRef — 是一个 React 钩子,它创建一个带有 .current 属性的可变 ref 对象,该对象在组件的渲染之间保持不变。与 useState 不同,修改 .current 不会触发重新渲染,这使得 useRef 成为存储 DOM 引用、定时器以及任何需要在渲染之间持久化而不重绘 UI 的值的理想选择。根据 React Documentation (2025),ref 对象在组件的生命周期内只创建一次,并且在渲染之间不会改变,从而保证了引用的稳定性。
要点
useRef — 是在 React 16.8 中添加的一个钩子,它返回一个带有单一属性 .current 的可变 ref 对象。初始值在组件挂载时分配给 .current。与普通变量的主要区别在于 — ref 对象在重新渲染时保持不变:重新渲染时,React 不会重新创建 ref,而是返回同一个对象。
useRef 的内部实现使用与 useState 和 useEffect 相同的机制 — 在 fiber 节点上的钩子链表。然而,与 useState 不同,React 不追踪 ref 对象的更改,不将更新排队,也不安排重新渲染。这使得 useRef 成为一个极其轻量的钩子,即使频繁更改也不会影响性能。
根据 React Team — Hooks FAQ (2025),useRef 本质上是一个存储值的 “盒子”。语法糖:const ref = useRef(initialValue) 等同于 const ref = { current: initialValue },区别在于 React 保证 ref 对象在每次渲染时都是同一个。没有魔法 — 只是一个由 React 管理的 稳定引用。
import { useRef } from 'react';
function Component() {
const countRef = useRef(0);
// countRef.current = 0 初始状态
// countRef.current = 5 修改后
// 不会发生重新渲染!
}
useRef 最常见的使用场景 — 获取对 DOM 元素的直接访问。在组件挂载后,React 将 elementRef.current 赋值为对 DOM 节点的引用。这对于聚焦输入框、测量元素尺寸、与动画库以及不使用 React 渲染的库进行集成是必要的。
React 自动管理对 DOM 的 ref 引用:挂载时分配实际的 DOM 节点,卸载时将其重置(设为 null)。这保证了 .current 始终包含最新值,并防止通过指向已删除元素的 “悬挂” 引用导致的内存泄漏。无需手动清理。
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载后聚焦输入框
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
根据 React — Refs and DOM documentation (2025),不要将 ref 用于声明式操作 — 打开/关闭模态框、管理可见性。这些目的有 state 和 props。Refs 专用于无法以声明方式表达的命令式操作:聚焦、文本选择、与第三方库集成。
第二个最重要的场景 — 存储任意值,这些值需要在渲染之间保持,但它们的更改不应触发重新渲染。典型示例:定时器标识符(setInterval/setTimeout)、用于取消请求的标志、用于比较的先前 prop 值以及任何不影响 UI 输出的数据。
这在 useEffect 中特别有用:useRef 存储定时器标识符,而 cleanup 将其清除。如果 timerId 存储在 useState 中,每次调用 setTimerId 都会导致不必要的重新渲染,这对于逻辑或 UI 都不需要。useRef 在没有开销且没有 不必要的重绘 的情况下解决了这个问题。
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
console.log('滴答');
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
useEffect(() => stop, []);
return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}
主要困境 — 在 useRef 和 useState 之间选择 — 可以通过一个问题来解决:“值更改时是否需要重新渲染?”。如果需要 — useState。如果不需要 — useRef。useState 存储影响组件输出的状态;useRef 存储内部逻辑所需但不会影响 UI 的数据。
在实践中,开发人员经常使用 useRef 来存储回调,以避免闭包问题。例如,如果 useEffect 订阅了一个事件,并且在回调内部需要最新的 state — 将回调存储在 useRef 中。每次渲染时用新函数更新 ref.current,效果将始终调用最新的回调而无需重新订阅。
| 标准 | useRef | useState |
|---|---|---|
| 重新渲染 | 更改时不触发 | 每次 setState 时触发 |
| 修改 | 直接:ref.current = value | 通过 setter:setState(value) |
| 在 JSX 中使用 | 不使用(无效果) | 在组件输出中使用 |
| 示例 | 定时器、DOM 引用、先前值 | 表单数据、UI 状态、标志 |
存在反模式:使用 useRef 存储 JSX 中需要的数据,但更改不应触发重新渲染。这会导致不同步 — UI 显示旧数据,而 ref.current 已经更新。如果值在 UI 中显示 — 使用 useState。如果仅在逻辑内部使用 — useRef。
useRef + useEffect 的组合 — 用于跟踪先前 prop 值的标准模式。将先前值存储在 ref 中,在 useEffect 中与当前值进行比较,并根据差异做出决定。这在动画中特别有用,当需要知道更改前的值是什么时。
function PriceDisplay({ price }) {
const prevPriceRef = useRef(price);
useEffect(() => {
const prevPrice = prevPriceRef.current;
if (price > prevPrice) {
animateUp();
} else if (price < prevPrice) {
animateDown();
}
prevPriceRef.current = price;
}, [price]);
return <span>${price}</span>;
}
根据 React Documentation — Hooks FAQ (2025),此模式称为 “previous value pattern”。它之所以有效,是因为 useRef 在渲染之间保持值,而 useEffect 在更改确认后执行。首先用新的 price 更新 DOM,然后 useEffect 与先前值(仍在 ref 中)进行比较,并将 ref.current 更新为当前值。
最常见的错误 — 在渲染阶段读取 ref.current 以计算 JSX。由于更改 ref.current 不会触发重新渲染,组件可能使用过时的值。如果 .current 的值参与 UI 输出 — 使用 useState。如果需要同步 ref 和 state,使用 useEffect 从 ref 更新 state。
// ❌ 不要在渲染期间读取 ref.current 来显示内容
function BadComponent() {
const valRef = useRef(0);
return <p>{valRef.current}</p>; // 修改时不会更新
}
// ✅ 使用 state 显示,使用 ref 处理逻辑
function GoodComponent() {
const [val, setVal] = useState(0);
const valRef = useRef(0);
return <p>{val}</p>;
}
另一个常见错误 — 当数据确实影响 UI 时,却将 useRef 作为组件中存储状态的唯一方式。开发人员选择 useRef 以避免 “不必要” 的重新渲染,但最终得到一个不会更新的 UI。正确的方法:对 UI 数据使用 useState,对不参与渲染的辅助值仅使用 useRef。
常见问题
是的,这是一个常见的模式 — previous state pattern。创建一个 ref 并在 useEffect 中每次跟踪的值更改时更新它。在渲染和效果之间的时刻,ref.current 包含可以与当前值比较的先前值。这不需要额外的重新渲染,并且适用于所有数据类型。
React 只有在元素被渲染并添加到真实 DOM 后,才会将 DOM 节点的值赋给 ref.current。在首次渲染期间,组件尚未挂载,因此 ref.current 等于初始值(null)。通过 ref.current 访问 DOM 仅在 useEffect 或在挂载后调用的事件处理程序中可能。
createRef 在每次渲染时创建一个新的 ref 对象 — 只能在类组件中使用。useRef 只创建一次 ref,并在所有后续渲染中返回同一个对象。在函数式组件中,请只使用 useRef;createRef 会导致重新渲染时值丢失,因为对象将被重新创建。
是的,ref 对象可以作为普通 prop 传递:<Child inputRef={inputRef} />。子组件通过 inputRef.current 使用它。然而,要将 ref 直接传递给子组件的 DOM 元素,请使用 forwardRef — 一个允许 ref 通过 props 传递的高阶组件。没有 forwardRef,ref 不会自动绑定到子组件内部的 DOM 元素。
将 ref 绑定到元素,然后在 useEffect 中读取 ref.current.getBoundingClientRect() 或 ref.current.offsetWidth / offsetHeight。对于响应式尺寸变化跟踪,在 useEffect 中使用 ResizeObserver:创建一个观察器,订阅变化,并用新尺寸更新 state。不要忘记在 cleanup 中禁用观察器。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。