人人都会AI编程

useRef:DOM 引用与跨渲染周期变量存储

更新时间:2026-07-10

useRef 是 React 提供的一个 Hook,它返回一个可变的 ref 对象,该对象在组件的整个生命周期中持续存在。最常见的两个用途是:获取 DOM 元素的引用,以及存储不触发重新渲染的可变数据。

基本语法

import { useRef } from 'react';

function MyComponent() {
  const refContainer = useRef(initialValue);
  // refContainer.current 持有当前值
}

useRef 接受一个初始值作为参数,并返回一个带有 current 属性的普通 JavaScript 对象。这个对象在每次渲染时都是同一个引用,修改 .current 不会触发组件重新渲染。

用途一:获取 DOM 元素的引用

当你需要在 React 中直接操作 DOM(例如聚焦输入框、测量元素尺寸、集成第三方非 React 库)时,useRef 是最标准的方式。

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // 组件挂载后自动聚焦输入框
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} placeholder="自动聚焦" />;
}

关键点:

  • 将 ref 对象传给 JSX 元素的 ref 属性,React 会在组件挂载后将该元素的 DOM 节点赋值给 ref.current
  • 组件卸载时,ref.current 自动重置为 null
  • 不要试图在渲染期间读取或修改 ref.current(除非涉及 ref 回调函数等特定场景),否则可能导致行为不一致。

常用 DOM 操作示例:

function VideoPlayer() {
  const videoRef = useRef(null);

  const handlePlay = () => {
    videoRef.current?.play();
  };

  const handlePause = () => {
    videoRef.current?.pause();
  };

  return (
    <div>
      <video ref={videoRef} src="movie.mp4" />
      <button onClick={handlePlay}>播放</button>
      <button onClick={handlePause}>暂停</button>
    </div>
  );
}

用途二:存储跨渲染周期的可变变量

如果你需要在整个组件生命周期内保持某个可变值,又不需要它触发重新渲染,useRef 是最佳选择。典型场景包括:

  • 保存定时器 ID
  • 保存上一次的 props 或 state(用于比较变化)
  • 记录某些副作用执行次数
  • 存储不需要显示在 UI 上的“临时”数据

保存定时器 ID

function Timer() {
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    // 避免重复启动
    if (timerRef.current !== null) return;
    timerRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(timerRef.current);
    timerRef.current = null;
  };

  // 组件卸载时清理定时器
  useEffect(() => {
    return () => clearInterval(timerRef.current);
  }, []);

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={startTimer}>开始</button>
      <button onClick={stopTimer}>停止</button>
    </div>
  );
}

这里 timerRef 用来保存定时器 ID,修改它不会导致组件重新渲染,并且在多次渲染之间值保持不变。

记录上一次的 state(实现 componentDidUpdate 比较)

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>当前:{count}</p>
      <p>上一次:{prevCount}</p>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
    </div>
  );
}

useRef 保存了上一次渲染时的 count,通过比较 prevCountcount 可以执行特定逻辑,而无需将旧值存入 state(避免无意义的重渲染)。

存储不需要渲染的数据(例如复杂计算结果的缓存)

function ExpensiveComponent({ data }) {
  const computedCache = useRef(null);

  const getComputedValue = () => {
    if (computedCache.current) return computedCache.current;
    // 假设这是一个非常耗时的计算
    const result = expensiveCalculation(data);
    computedCache.current = result;
    return result;
  };

  return <div>{getComputedValue()}</div>;
}

注意:在并发模式下,这种手动缓存可能因为意外被多次调用而产生问题,更推荐使用 useMemo 来缓存计算结果。但如果计算结果不需要依据依赖变化重新计算,或者你希望完全手动控制缓存,useRef 是一个合理的兜底方案。

useRef 与 useState 的区别

| 特性 | useRef | useState |
|------|--------|----------|
| 返回值的变更 | 修改 .current 不会触发重渲染 | 调用 setter 会触发重渲染 |
| 读取时效性 | 总是在任何时刻读取到最新值(包括在异步回调中) | 在闭包中可能捕获旧的 state(过期闭包) |
| 典型用途 | DOM 引用、定时器 ID、任意可变持久化值 | UI 状态,直接与视图绑定的数据 |

关键洞察:
useRef 可以看作是 useState 的“渲染非敏感”版本。它完美解决了两个痛点:

  1. 需要持久化一个值,但又不想因为它的变化而重新渲染。
  2. 在闭包(如定时器回调、事件监听器)中需要始终读取到该值的最新内容,而不会像 state 那样受闭包陷阱影响。

常见陷阱与注意事项

1. 避免在渲染期间写入或读取 ref.current(用于 DOM 时除外)
React 的渲染应该是纯函数,如果在渲染期间修改 ref,可能与其他渲染副作用产生冲突,破坏并发渲染的安全性。正确的做法是在事件处理函数或副作用(useEffect)中操作 ref。

2. 不要用 ref 来代替 state
如果某个值的变化需要反映到 UI 上,那么它应该是 state(或派生 state)。ref 仅用于不影响视图的“幕后”数据。

3. 使用回调 ref 获取动态元素
当 ref 需要附加到条件渲染的元素上时,useRef 配合 ref 属性有时不够灵活。可以使用 callback ref 来获取变化的元素:

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  }, []);

  return (
    <div ref={measuredRef}>
      <h1>Hello, world</h1>
    </div>
  );
}

总结

useRef 是一个简单但功能强大的 Hook,适用于两种核心场景:

  • 获取 DOM 节点:操作原生 DOM,如聚焦、滚动、动画等。
  • 存储不触发渲染的可变值:保存任何需要在组件生命周期内保持可变的引用,避免闭包陷阱,同时不引入额外的渲染。

合理使用 useRef 可以让你的组件逻辑更清晰、性能更佳,避免不必要的 state 和渲染开销。