useRef — 是什么,引用钩子以及在 React 中操作 DOM

作者: IT Sectr 发布日期: 2026-07-04 阅读时间: 9 分钟

useRef — 是一个 React 钩子,它创建一个带有 .current 属性的可变 ref 对象,该对象在组件的渲染之间保持不变。与 useState 不同,修改 .current 不会触发重新渲染,这使得 useRef 成为存储 DOM 引用、定时器以及任何需要在渲染之间持久化而不重绘 UI 的值的理想选择。根据 React Documentation (2025),ref 对象在组件的生命周期内只创建一次,并且在渲染之间不会改变,从而保证了引用的稳定性。

要点

  • useRef — 用于创建带有 .current 字段的可变 ref 对象的钩子。
  • 无重新渲染 — 修改 .current 不会导致组件重绘。
  • DOM 引用 — 传递给元素的 ref prop 以直接访问 DOM 节点。
  • 存储值 — 适用于定时器、先前值以及渲染之间的任何数据。
  • 稳定引用 — 与闭包不同,ref 对象在渲染之间不会改变。

React 中的 useRef 是什么

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 管理的 稳定引用

jsx
import { useRef } from 'react';

function Component() {
    const countRef = useRef(0);
    // countRef.current = 0 初始状态
    // countRef.current = 5 修改后
    // 不会发生重新渲染!
}

通过 useRef 访问 DOM 元素

useRef 最常见的使用场景 — 获取对 DOM 元素的直接访问。在组件挂载后,React 将 elementRef.current 赋值为对 DOM 节点的引用。这对于聚焦输入框、测量元素尺寸、与动画库以及不使用 React 渲染的库进行集成是必要的。

React 自动管理对 DOM 的 ref 引用:挂载时分配实际的 DOM 节点,卸载时将其重置(设为 null)。这保证了 .current 始终包含最新值,并防止通过指向已删除元素的 “悬挂” 引用导致的内存泄漏。无需手动清理。

jsx
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 在没有开销且没有 不必要的重绘 的情况下解决了这个问题。

jsx
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 vs useState:何时使用哪个

主要困境 — 在 useRef 和 useState 之间选择 — 可以通过一个问题来解决:“值更改时是否需要重新渲染?”。如果需要 — useState。如果不需要 — useRef。useState 存储影响组件输出的状态;useRef 存储内部逻辑所需但不会影响 UI 的数据。

在实践中,开发人员经常使用 useRef 来存储回调,以避免闭包问题。例如,如果 useEffect 订阅了一个事件,并且在回调内部需要最新的 state — 将回调存储在 useRef 中。每次渲染时用新函数更新 ref.current,效果将始终调用最新的回调而无需重新订阅。

标准useRefuseState
重新渲染更改时不触发每次 setState 时触发
修改直接:ref.current = value通过 setter:setState(value)
在 JSX 中使用不使用(无效果)在组件输出中使用
示例定时器、DOM 引用、先前值表单数据、UI 状态、标志

存在反模式:使用 useRef 存储 JSX 中需要的数据,但更改不应触发重新渲染。这会导致不同步 — UI 显示旧数据,而 ref.current 已经更新。如果值在 UI 中显示 — 使用 useState。如果仅在逻辑内部使用 — useRef。

useRef 与 useEffect 的组合

useRef + useEffect 的组合 — 用于跟踪先前 prop 值的标准模式。将先前值存储在 ref 中,在 useEffect 中与当前值进行比较,并根据差异做出决定。这在动画中特别有用,当需要知道更改前的值是什么时。

jsx
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 更新为当前值。

useRef 的常见错误

最常见的错误 — 在渲染阶段读取 ref.current 以计算 JSX。由于更改 ref.current 不会触发重新渲染,组件可能使用过时的值。如果 .current 的值参与 UI 输出 — 使用 useState。如果需要同步 ref 和 state,使用 useEffect 从 ref 更新 state。

  • JSX 中的 Ref — 在渲染函数内读取 ref.current 会导致 UI 和数据的同步问题。
  • Ref 作为 useEffect 的依赖项 — ref.current 不应出现在依赖数组中;React 不追踪其更改。
  • 忘记的空值检查 — 卸载时 ref.current 变为 null;访问前进行检查。
  • 在 ref 中存储函数 — 如果使用最新回调模式,每次渲染时更新 ref.current。
jsx
// ❌ 不要在渲染期间读取 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。

常见问题

可以用 useRef 来存储先前的状态吗?

是的,这是一个常见的模式 — previous state pattern。创建一个 ref 并在 useEffect 中每次跟踪的值更改时更新它。在渲染和效果之间的时刻,ref.current 包含可以与当前值比较的先前值。这不需要额外的重新渲染,并且适用于所有数据类型。

为什么首次渲染时 ref.current 是 null?

React 只有在元素被渲染并添加到真实 DOM 后,才会将 DOM 节点的值赋给 ref.current。在首次渲染期间,组件尚未挂载,因此 ref.current 等于初始值(null)。通过 ref.current 访问 DOM 仅在 useEffect 或在挂载后调用的事件处理程序中可能。

useRef 和 createRef 之间有什么区别?

createRef 在每次渲染时创建一个新的 ref 对象 — 只能在类组件中使用。useRef 只创建一次 ref,并在所有后续渲染中返回同一个对象。在函数式组件中,请只使用 useRef;createRef 会导致重新渲染时值丢失,因为对象将被重新创建。

可以通过 props 传递 useRef 吗?

是的,ref 对象可以作为普通 prop 传递:<Child inputRef={inputRef} />。子组件通过 inputRef.current 使用它。然而,要将 ref 直接传递给子组件的 DOM 元素,请使用 forwardRef — 一个允许 ref 通过 props 传递的高阶组件。没有 forwardRef,ref 不会自动绑定到子组件内部的 DOM 元素。

如何使用 useRef 测量元素的尺寸?

将 ref 绑定到元素,然后在 useEffect 中读取 ref.current.getBoundingClientRect() 或 ref.current.offsetWidth / offsetHeight。对于响应式尺寸变化跟踪,在 useEffect 中使用 ResizeObserver:创建一个观察器,订阅变化,并用新尺寸更新 state。不要忘记在 cleanup 中禁用观察器。

总结

  • useRef — 用于创建可变 ref 对象的钩子,该对象在渲染之间保持不变,且不会触发重新渲染。
  • DOM 访问 — ref prop 传递给元素以直接访问 DOM 节点;挂载后可用。
  • 数据存储 — 将定时器、回调、标志和任何不影响 UI 的值存储在 ref 中。
  • useRef vs useState — 如果更改应重绘 UI,选择 useState;否则选择 useRef。
  • Previous value pattern — 将先前值存储在 ref 中,在 useEffect 中更新以进行比较。
  • JSX 中的 Ref — 不要在渲染阶段读取 ref.current;这会导致 UI 和数据不同步。
  • forwardRef — 用于通过 props 将 ref 传递给子组件。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读